Typorithm · easy

Functions, return contracts and fresh defaults

pythonscriptingdata-boundariesindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Functions, return contracts and fresh defaults

0%

Concept and contract

Keep repeated calls independent and avoid mutating an optional caller-owned collection.

A function's contract includes its arguments, returned value and observable side effects. The worked add_saved returns a new list containing a role identity once. If a saved list is supplied, its order is preserved; if none is supplied, each call starts from a fresh empty list.

Python evaluates default argument expressions when the function is defined, not afresh on every call. A list written directly as a default can therefore be shared across calls. That hidden state is especially confusing when two independent users or test cases expect separate results.

Mechanism and worked example

None acts as an omitted-collection marker for this particular API.

None acts as an omitted-collection marker for this particular API. The function creates a new list when the argument is None and otherwise copies the supplied list. It appends only when membership says the identity is not already present, then always returns the resulting list.

The sentinel choice depends on the contract: None is suitable because a literal None collection is not a meaningful separate payload here. If None were a legitimate supplied value, a distinct sentinel policy would be needed. The fresh-allocation rule remains separate from the returned ordering policy.

Complete worked example

python

def add_saved(role_id, saved=None):
    result = [] if saved is None else saved.copy()
    if role_id not in result:
        result.append(role_id)
    return result
Reasoning
Each omitted collection is newly allocated and supplied lists are copied.

Counterexamples and common mistakes

The counterexample uses a mutable empty-list default and appends into it.

The counterexample uses a mutable empty-list default and appends into it. Its first default call can look correct while the second inherits the earlier role. It also mutates a supplied caller list, so both repeated-call isolation and input preservation deserve tests.

Do not fix this by clearing shared state at arbitrary points. Make allocation and ownership explicit. Another common mistake returns only inside the append branch, causing an already-saved identity to produce None instead of the same kind of list result as every other call.

Counterexample: a plausible but incorrect implementation

python

def add_saved(role_id, saved=[]):
    if role_id not in saved:
        saved.append(role_id)
    return saved
Reasoning
The same default list survives across calls and accumulates unrelated values.

Independent practice: small Python task

Keep the fresh-default and non-mutation rules but return the unique saved identities in sorted order.

Keep the fresh-default and non-mutation rules but return the unique saved identities in sorted order. The small change is the final presentation policy. Predict r1 before r2 even when the supplied list originally contains r2 and the newly added identity is r1.

Preserve existing-list membership and do not introduce a set as a hidden storage model unless you can explain its order and value constraints. For this string identity contract, sorting the already deduplicated result is sufficient and easy to compare with the worked version.

Step 1: Retain fresh allocation and copying. Step 2: Sort the complete result before returning it.

Practice starter: solve before reading the solution

python

def add_saved(role_id, saved=None):
    # Preserve isolation and return sorted unique saved identities.
    raise NotImplementedError
Reasoning
Change the output ordering without changing collection ownership.
  • Hint 1: Do not change the function default to a list.
  • Hint 2: The duplicate branch should still reach the return.

Solution, tests and trade-offs

The solution returns sorted results while retaining fresh allocation and one-occurrence membership.

The solution returns sorted results while retaining fresh allocation and one-occurrence membership. Consecutive default calls are tested within the same loaded module so a shared-default defect cannot be hidden by re-importing the function before every case. Supplied input lists are checked unchanged as well.

These checks establish the stated behavior for string role identities, not a universal immutable collection API. If callers can pass arbitrary objects, identity normalization and equality rules need their own boundary. Keep the simple core task focused on ownership and repeatable calls.

Practice solution and acceptance checks

python

def add_saved(role_id, saved=None):
    result = [] if saved is None else saved.copy()
    if role_id not in result:
        result.append(role_id)
    return sorted(result)
Reasoning
Repeated default calls remain independent and input lists remain unchanged.

Interview reasoning and next step

In an interview, explain when defaults are evaluated and why returning a mutable object is not automatically wrong.

In an interview, explain when defaults are evaluated and why returning a mutable object is not automatically wrong. The issue is the promised ownership and sharing behavior. Show two calls and an existing saved list rather than relying on a slogan about never using lists.

The next topic expresses a clear loop as a comprehension and adds deterministic sorting. You will retain the same discipline: output shape, mutation and ordering should remain explicit even when the syntax becomes shorter.

Optional stretch — after the core task works: Design a keyword-only option for ordering while keeping both ownership and repeated-call tests.

Computer practice: https://applydjinn.com/typorithm/python-lab-20261105.zip. Run verify.py with your selected Python interpreter, then complete an exercise and check it with --practice followed by the lesson id. The lab uses only the standard library and disposable local fixtures; it never connects to the production database or company feeds.

Keep repeated calls independent and avoid mutating an optional caller-owned collection.