How do `/` and `*` markers stop a Python signature from making promises you cannot keep?
answer
- Which parts of a signature are promises
- Position is one promise, the name another
- Silent drift versus a loud TypeError
- Slash frees the name, star frees the order
- Subject positional-only, options keyword-only
basics
~20 sA positional-or-keyword parameter promises callers both a position and a name. / withdraws the name so you can rename freely; * withdraws the position so you can add and reorder options. Both turn future mistakes into TypeError.
solid answer
~50 sEvery ordinary parameter makes two commitments at once: its **position** in the argument list and its **name**. Both are public the moment someone calls the function, and both are things you will later want to change. `/` withdraws the name promise for the obvious operands, so renaming them is a private edit rather than a breaking release. `*` withdraws the position promise for options, so a new option can be inserted anywhere without shifting what an existing call means. The failure mode this prevents is the dangerous one: inserting a parameter ahead of an existing positional option does not raise anything — it silently redirects the caller's value into the wrong slot, so behaviour drifts instead of breaking. With the options keyword-only, that same call would have failed loudly at the call site. The cost is that the markers must be designed in early; adding either one later breaks existing callers.
code
python · 9 linesdef optimise_v1(points, tolerance=1e-9, max_iterations=100):
return tolerance, max_iterations
def optimise_v2(points, smoothing=0.0, tolerance=1e-9, max_iterations=100):
return tolerance, max_iterations
route = [(0.0, 0.0), (1.0, 1.0)]
print(optimise_v1(route, 1e-6))
print(optimise_v2(route, 1e-6))go deeper
Notice that where an argument sits in a call can matter as much as its value, and that naming your arguments at the call site protects you when a library's parameter order changes.
Explain concretely what breaks: inserting a parameter shifts positional callers silently, while renaming one breaks keyword callers loudly. Be able to say which marker removes which risk.
Bring a real diagnosis: how a shifted positional argument shows up as drifting results rather than an exception, and how you would design or repair the signature so the failure is immediate instead.
Own the convention and the migration policy — which parameter classes take which marker, how signature changes are versioned and announced, and why the team accepts a loud break over a quiet one.
## Two promises hide in every plain parameter Write `def optimise(points, tolerance=1e-9, max_iterations=100)` and you have published two contracts without deciding to. The first is **ordering**: `tolerance` is the second positional slot, forever. The second is **naming**: `points`, `tolerance` and `max_iterations` are identifiers callers may write, so they are as public as the function's own name. Both become load-bearing the first time somebody calls the function, and both are exactly what you want to change in the next release. `/` and `*` let you withdraw each promise deliberately, at design time, rather than discovering later that you made it by accident. ## The silent failure the `*` marker prevents Consider a route-optimisation library used by a batch job that runs for six hours every night. The call site reads `optimise(route, 1e-6)` — tolerance passed positionally, because it is short and the author knew the order. A later release adds a smoothing option and, quite reasonably, puts it next to the other geometric parameters: `def optimise(points, smoothing=0.0, tolerance=1e-9, max_iterations=100)` Nothing raises. The caller's `1e-6` now lands in `smoothing`, and `tolerance` quietly reverts to its much tighter default. The job still finishes, still writes routes, and the only symptom is a floating-point rounding drift in the results — a fraction of a percent of extra distance that the nightly run's summary does not flag. That is the worst class of API break, because there is no traceback pointing at the change and the bisect has to happen against six-hour runs. Had the options been keyword-only from the start — `def optimise(points, /, *, tolerance=1e-9, max_iterations=100)` — the original call `optimise(route, 1e-6)` would never have compiled into working code at all: it fails immediately with `TypeError: optimise() takes 1 positional argument but 2 were given`. The library author is then free to insert `smoothing` anywhere in the keyword-only region, reorder the options, or change a default's documentation, and no existing call can change meaning, because no existing call refers to those parameters by position. ## The rename the `/` marker enables The mirror-image problem is the name. If callers may write `optimise(points=route)`, then `points` is API, and renaming it to `waypoints` in a refactor is a breaking change to strangers' code for no user-visible benefit. Marking it positional-only — `def optimise(points, /, ...)` — says the identifier is internal. Nobody was ever going to want to name it; the function has exactly one obvious subject. This is why the standard library is written this way: `sorted(iterable, /, *, key=None, reverse=False)` is a whole design philosophy in one line — the subject positional-only, the options keyword-only, nothing in the ambiguous middle. ## The judgement calls **Do not mark everything.** A signature where every parameter is keyword-only makes the common call verbose and reads as ceremony; `sorted(iterable=rows)` is worse than `sorted(rows)`. The heuristic that holds up is: the one or two arguments that *are* the function's subject go positional-only; anything that tunes, configures or toggles behaviour goes keyword-only; the positional-or-keyword middle is where you leave the parameters you have not yet decided about, and it should be small. **Booleans are the strongest case for `*`.** `render(doc, True, False)` is unreadable and one transposition away from a bug that type checking cannot catch; keyword-only makes the readable form the only form. **Adding a marker later is a break.** Introducing `*` breaks callers who passed positionally; introducing `/` breaks callers who passed by name. The compensation is that both breaks are immediate `TypeError`s at the call site rather than silent behaviour changes, so if you must tighten a signature, do it at a version boundary, announce it, and accept the loud failure as the price of never shipping the quiet one. **Decide once, as a convention.** The value of the rule is mostly in not relitigating it per function in review. A team that writes "subject positional-only, options keyword-only" into its style guide removes an entire class of compatibility break from its future, and gets more readable call sites for free.
- When is positional-only the wrong choice for a parameter?Whenever the name helps the reader. `strict`, `encoding`, `timeout` and any boolean are clearer written out at the call site, so they belong after a `*`, not before a `/`. Positional-only suits the one or two arguments that are the function's obvious subject and that nobody would name even if allowed to.
- How would you tighten an existing public signature without breaking every caller at once?Do it at a version boundary and be explicit. Leave the existing positional parameters where they are, add every new option after a `*` so it can never be positional, and only in a major release move older parameters behind the markers. The break is a TypeError at the call site, which is loud and greppable — far better than a silent redirection of values into different slots.
- Why is a loud TypeError preferable to a silently shifted argument?Because a TypeError names the file, line and function at the moment of the call, so the fix is mechanical. A shifted argument produces plausible output with wrong parameters, which surfaces later as a drift in results, is not attributable to any single change, and often has to be bisected against expensive runs.
saying these in an interview costs you the question
- Treats every parameter name as free to rename
- Adds a new parameter in the middle of the positional list
- Says keyword-only is only a style preference
- Prefers a silent behaviour change to a TypeError
- Marks everything keyword-only, making calls verbose
- Thinks markers can be added freely to a published function