skip to content

After a food-delivery app upgrades its shared component library from 4.6.2 to 4.7.0, the quantity stepper silently caps orders at 10 instead of 99; what went wrong, and how should the library team respond?

level: seniorimportance: should knowfreq 40%

answer

  1. no code changed on the consumer side
  2. defaults are part of the promise
  3. published versions are immutable
  4. restore, flag the bad version
  5. opt-in now, flip in the major

basics

~20 s

A changed default is a breaking change shipped as a MINOR. Leave 4.7.0 as published, release a version restoring 99, flag 4.7.0 as faulty, and deliver the new cap as an opt-in or in 5.0.0.

solid answer

~50 s

The stepper's default maximum is part of its public API: every call site that never set it relied on 99. Changing it in 4.7.0 broke those consumers with no type error or warning, and any version range that accepts minor updates pulled it in automatically. Semantic versioning forbids modifying a published version, so the fix is a new release - the spec's FAQ suggests a minor - that restores the old default, plus a notice naming 4.7.0 as the offending version. If the team still wants the lower cap, ship it as an opt-in property now and flip the default in 5.0.0 with a clear change entry. To prevent a repeat, make defaults reviewable: a generated report of every component's properties and defaults diffed on each change, and one release question - what does a consumer who passes nothing now get?

go deeper

for a junior

Recall that a default is part of what a component promises, so changing one can break apps whose code never changed.

for a middle

Explain why no compiler or type check catches a changed default, and why version ranges delivered it to consumers automatically.

for a senior

Walk through the response: leave the bad version untouched, release a restoring version, flag the offender, then ship the change as opt-in and flip it in a major.

for a principal

Discuss how to make defaults reviewable at scale - generated API reports, labelled change entries - and when restoring behavior itself deserves a major signal.

## Why a default is API A **default** is the value a component uses when the consumer does not pass one. Semantic versioning bumps MAJOR for any backward-incompatible change to the **declared public API**, and for a component library that API includes defaults: a team that writes a quantity stepper without a maximum is relying on the documented default just as surely as a team that passes one. When 4.7.0 lowered the default cap from 99 to 10, every such call site changed behavior. Nothing failed to build, no type checker complained, and the change arrived automatically through version ranges that accept minor updates. Catering orders of 30 meals simply stopped being possible. This is the most insidious kind of break: the consumer did everything right and got a different product. ## Diagnosing it 1. **Confirm the regression window** - the behavior is correct on 4.6.2 and wrong on 4.7.0, with no app code change in between. 2. **Read the 4.7.0 change entries** for the stepper. If the default change is listed as a feature or a fix rather than a breaking change, the classification is the bug. 3. **Diff the component's public surface** between the two versions - properties, types, defaults, events - to confirm nothing else moved. 4. **Estimate blast radius** - which consuming apps use the stepper without setting a maximum, on web and native mobile alike. ## Responding without making it worse The semantic versioning FAQ covers exactly this case - a backward-incompatible change accidentally released as a minor: 1. **Do not modify 4.7.0.** The spec says released contents must not be modified; republishing the same number with different content breaks every cache and lockfile that already recorded it. 2. **Release a new version that restores compatibility** - the FAQ suggests a new minor, so 4.8.0 with the default back at 99. 3. **Document the offending version** and tell consumers, so teams know to skip 4.7.0 rather than debug it. 4. **Use judgment about the audience.** The FAQ notes that if a large audience would be drastically affected by restoring the old behavior, a major release may be the better signal. Here most consumers expect 99, so restoring it is right. ## Shipping the new cap properly | Option | Release | Effect on existing consumers | |---|---|---| | Add an opt-in property for the lower cap | MINOR | None until they opt in | | Change the default | MAJOR (5.0.0) | Must review call sites that relied on 99 | | Opt-in now, flip the default in the next major | MINOR then MAJOR | Time to migrate, with a clear change entry | The third path is usually best: teams that want the cap get it today, and the eventual default change arrives with the one version number consumers read carefully. ## Preventing the next one - **Generate an API report** - each component's properties, types and defaults - commit it, and diff it on every change, so a changed default becomes a visible line a reviewer must classify. - **Label every change entry** patch, minor or major, and require a reviewer to confirm the label. - **Ask one review question** for every behavioral change: *what does a consumer who passes nothing now get?* - **Pin behavior in tests** that exercise defaults, so an accidental change fails before release. - **Encourage consumers to set business rules explicitly.** An order ceiling is a product decision, and making it explicit protects the app - but it never excuses the library from versioning its defaults honestly. The lesson for interviews: breaking changes are defined by consumer impact, not by what the diff looks like. A one-character edit to a default can be the biggest break in a release.

  • Some teams already adapted to the new cap of 10. Does restoring 99 break them?
    It can, which is why the semantic versioning FAQ says to use judgment: if restoring the old behavior would drastically affect a large audience, a major may be the better signal. Here most consumers still expect 99, so restoring it is right; the few who adapted should set the cap explicitly, which works on every version.
  • Should consumers have set the maximum explicitly all along?
    For a limit that encodes a business rule, setting it explicitly is wise: a catering order ceiling is a product decision, not the library's. But that does not excuse the library. A consumer relying on a documented default is using the API correctly, so the library still owes a MAJOR when it changes one.
  • How would you catch a changed default in review before release?
    Generate a machine-readable report of each component's public surface - properties, types, defaults, events - commit it, and diff it on every change. A changed default then shows up as a one-line diff a reviewer must classify, instead of hiding inside an implementation change.

Like a cafe that quietly changes its default cup from large to small: customers who always order 'a coffee' without naming a size get less, although none of them changed their order.

saying these in an interview costs you the question

  • No property was removed, so a default change is safe in a MINOR.
  • Just republish 4.7.0 with the old default restored.
  • The consumers relied on a default, so the break is their fault.
  • Quietly revert without telling consumers which version was faulty.
  • Restoring the old default must be a MAJOR because it changes behavior.