Learn the concept
A function names a reusable operation. Parameters describe the required inputs; return provides a result to the caller. Printing a value displays it but does not return it. Keep domain functions independent from terminal input and output so they can be tested directly.
Local names belong to a function call. Reading global configuration can be convenient, but changing global state makes behavior harder to reproduce. Pass dependencies explicitly when a function needs a client, clock, or repository.
Keyword arguments make call sites clearer when several values have the same type. Defaults should be safe and intentional. A mutable default list is created once when the function is defined, so using None and allocating inside the function avoids accidentally sharing values across calls.
Run and inspect
def add_tag(tag, existing=None):
result = list(existing) if existing is not None else []
result.append(tag)
return result
assert add_tag("a") == ["a"]
assert add_tag("b") == ["b"]
base = ["x"]
assert add_tag("y", base) == ["x", "y"]
assert base == ["x"]
Your exercise
Extract validation and formatting into separate functions that return results. Test calls in a different order to detect unwanted shared state.
Check your understanding
The same inputs produce the same outputs and no caller-owned list is modified unexpectedly.