How does the Principle of Least Astonishment apply to default values and default behavior in configuration and API design, and when should a "safe" default win over an "expected" default?
answer
- default = the choice made for everyone who doesn't choose
- conventional, common-case correct, safe
- safety beats convenience; opt-out must be loud
- no timeout means wait forever
- changing a default is a breaking change
basics
~20 sDefaults are what most people actually get, so they must match the common case and never hide risk. Pick the value an informed user would have chosen; when the expected default is dangerous — no timeout, verification off — choose the safe one and make the risky option explicit.
solid answer
~50 sDefaults are decisions you make on behalf of every caller who does not think about them, so under Least Astonishment they must pass three tests. **Conventional**: match the ecosystem's norm, because callers arrive with that expectation. **Common-case correct**: serve the majority workload, so the code written most often is also the code that is right. **Safe**: when convenience and safety conflict, default to the conservative option — encryption on, certificate verification on, a finite timeout, bounded retries, least privilege — and require an explicit, verbose opt-out. Silent defaults that trade away durability, security, or availability are the worst kind of surprise because nothing at the call site records the decision. Related surprises to avoid: defaults that differ per environment, defaults that change meaning between versions, and "no default" masquerading as one (an unset timeout meaning infinity). Where the right value is genuinely context-dependent, make the parameter required rather than guessing.
go deeper
Say defaults should match the common case and never be dangerous; give one example such as an HTTP client with no timeout.
Give the conventional / common-case / safe tests, several concrete bad defaults, and the rule that changing a default is a breaking change.
Discuss defaults as risk decisions made for absent callers, effective-config visibility, deprecation strategy for changing them, and when to make a parameter required instead.
Frame defaults as organization-wide policy levers — secure-by-default platform baselines, blast radius of a shared default change, migration mechanics across many consumers, and balancing guardrails against override fatigue.
## Why defaults matter more than options A default is the choice made by everyone who does not make a choice — in practice, the large majority of callers. Documented options are read by few; defaults ship everywhere. That makes them the highest-leverage place to apply Least Astonishment. ## Three tests for a default 1. **Conventional.** Does it match what the surrounding ecosystem does? If every HTTP client in the language follows redirects by default, yours not following them will bite people even if not following is arguably purer. 2. **Common-case correct.** Does the shortest possible call do the right thing for the most frequent scenario? Good design makes the easy path the correct path. 3. **Safe.** If the default is wrong, how bad is it? Asymmetric risk should tip the decision toward the conservative option. When (1) and (3) conflict, **safety wins** and you pay for it with an explicit escape hatch. Defaults the industry has moved on from: plaintext transport, permissive CORS, wide-open bind addresses, world-readable storage buckets, disabled certificate validation "for convenience", unlimited request bodies. ## Classic astonishing defaults - **Infinite timeouts.** "No timeout configured" usually means *wait forever*, which under load turns one slow dependency into a full outage. A finite default is less astonishing than an unbounded wait. - **Unbounded retries or no backoff.** Silent retries amplify an incident and can duplicate non-idempotent work. - **Unbounded queues, caches, or page sizes.** A list endpoint that returns everything when no page size is given is a memory and latency bomb. - **Locale/timezone/charset inherited from the host.** Behavior then differs between a laptop and a server for reasons invisible in the code. Default explicitly (UTC, UTF-8) instead of inheriting ambient state. - **Ambiguous units.** A default of `30` for a timeout is meaningless without units; name or type it. - **Auto-creating things.** "If the table/topic/directory does not exist, create it" is delightful in development and astonishing in production. ## Making defaults honest - **Explicit over implicit.** Expose or log the effective configuration at startup so the value in force is observable, not guessed. - **Same default everywhere.** A different default in dev than in prod guarantees a surprise at the worst moment. - **No default at all** is a legitimate choice: if no value is right for most callers, make the parameter required so the caller must decide. A required parameter is a small annoyance; a wrong silent default is an incident. - **Changing a default is a breaking change**, even when the signature is unchanged, because existing callers' behavior changes without them touching a line. Treat it like removing a method: announce it, offer a transition period, keep the old behavior reachable explicitly. - **Make opt-out loud.** `insecureSkipVerify = true` reads as a warning at the call site and in review; a defaulted `verify = false` hides the same decision. ## Trade-offs Strict, safe defaults add friction and generate support questions ("why did my request time out?"). That friction is usually a feature — it surfaces a decision that would otherwise be discovered during an outage. But it can be overdone: defaults so conservative that nearly everyone overrides them are not defaults, they are ceremony, and the universal override quickly becomes copy-pasted without thought. Aim for the value an informed user would pick, then make the risky alternative explicit and searchable.
- You need to change a widely used library's default from "retry forever" to "retry three times". How do you roll it out without astonishing existing users?Treat it as a breaking behavioral change: announce it, ship a release that warns when the old default is in effect and names the upcoming value, let callers set the value explicitly in advance, change the default only on a major version with prominent release notes, and keep the old behavior reachable through an explicit setting.
- When is having no default better than picking one?When no single value is right for most callers and a wrong guess is costly — a cache TTL, a partition key, a currency, a retention period. Forcing an explicit argument converts a silent, invisible decision into a visible one at every call site.
Factory settings on a power tool: guard on, speed low. Anyone can remove the guard deliberately, but nobody removes it by not thinking about it.
saying these in an interview costs you the question
- "The default is documented, so nobody will be surprised" — defaults are precisely what people use without reading
- Defaulting a timeout to unlimited because "we don't want to break long requests"
- Deriving defaults from ambient host state (locale, timezone, charset) and calling it flexible
- Thinking a default change is non-breaking because the signature did not change
- Defaulting to auto-create or auto-migrate behavior on production paths
- Choosing defaults so conservative that everyone overrides them, then treating the overrides as informed choices