A currency-conversion microservice occasionally fails. A developer adds a fallback that returns a hardcoded exchange rate of 1.0 (treating the foreign amount as if it were already in the local currency) whenever the call fails, so the checkout flow never breaks. What's wrong with this specific choice of default value, and what would be a safer approach?
answer
- fail open vs fail closed
- default value looks healthy but may be wrong
- risk scales with financial/legal impact
- silent failure = no alert fires
- reasonable default only for cosmetic/optional fields
basics
~20 sPretending the exchange rate is always 1.0 can massively over- or under-charge customers without anyone noticing — it fails silently in a way that looks like success. A safer default is to block or clearly flag the transaction instead of guessing.
solid answer
~40 sThe problem isn't using a fallback — it's picking a default that's silently plausible but can be wildly wrong for a value that directly affects money changing hands; a 1.0 rate could massively undercharge or overcharge a customer with no error signal at all. For correctness-sensitive values like this, the safer pattern is to fail loud on that specific step (reject or hold the transaction, ask the user to retry) rather than default-and-proceed, or to fall back to the last known good rate with a tight staleness bound and a visible 'rate may be slightly stale' disclosure, not an arbitrary constant. The general rule: default values are safe for cosmetic/optional data and dangerous for anything financial, legal, or safety-related.
go deeper
Should recognize that a wrong-but-valid-looking default can be worse than an error, using this scenario as the concrete example.
Should be able to articulate the fail-open vs fail-closed distinction and apply it to pick a safer fallback for a financial field.
Should design monitoring/alerting for fallback usage as part of the same change, and set policy for which categories of fields get which treatment.
Should establish org-wide guardrails (review requirements, sanity-check assertions, reconciliation processes) preventing correctness-critical fields from ever getting a naive constant fallback.
## What a default-value fallback is A **default-value fallback** is the simplest form of degradation: when a call fails, instead of propagating the error, the code substitutes a fixed, precomputed, or hardcoded value and continues as if the call had succeeded. Mechanically this is usually a single line — a catch block, a null-coalescing operator, or a config-driven default — which makes it tempting to add casually. The value returned can be: - a genuinely reasonable stand-in (e.g. defaulting a missing 'estimated read time' to a rounded average), or - as in this scenario, a value that happens to keep the code path running but has no real relationship to the true answer. ## Why defaults exist Defaults exist to keep a request completing rather than throwing when a non-essential field is unavailable, and they're extremely cheap to implement — no cache infrastructure, no retry logic, just a constant. For genuinely optional or cosmetic fields (a missing avatar, an unset nickname, a recommendation score), a default is often the right level of investment; building cache-based fallback infrastructure for it would be over-engineering. ## The trade-off The trade-off is between **engineering simplicity** and **correctness risk**, and the risk scales directly with how much the value matters downstream. - A default of `0` for a display counter is harmless. - A default of `1.0` for an exchange rate, or `true` for an 'is this address valid' check, or `0` for a shipping cost, directly changes money, legal compliance, or physical fulfillment outcomes. And because the fallback path returns a normal-looking, well-typed value rather than an error, none of the normal error-handling/alerting downstream ever fires. The system looks entirely healthy from the outside while quietly doing the wrong thing. This is strictly worse than a stale-cache fallback, which is at least usually close to correct; an arbitrary constant default has no such guarantee. ## How it fails in production In production this shows up as a slow-burning financial or trust incident rather than a visible outage: the fallback rate of 1.0 fires for some fraction of foreign-currency checkouts over days or weeks, generating either a wave of customer complaints about wrong charges or, worse, a wave of self-inflicted losses that finance discovers during reconciliation, long after the root-cause window has passed. Because the failure is invisible in logs that only track errors, it's frequently caught only through anomaly detection on business metrics rather than engineering monitoring, which delays diagnosis. A related failure mode is **scope creep**: a default that was reasonable when first added silently becomes load-bearing years later after the 'temporary' fallback is forgotten and the underlying service's reliability degrades. ## The safer pattern The safer pattern for correctness-critical values is to distinguish **'fail open'** from **'fail closed'** and pick deliberately per field rather than defaulting to fail-open everywhere for convenience: | Choice | When it applies | |---|---| | 'fail open' | proceed with a default, acceptable for optional data | | 'fail closed' | reject/hold and surface an error, required for financial/legal/safety-critical data | For the exchange-rate case specifically, a defensible fallback is the last known good rate served for a strictly bounded window (e.g. under 5 minutes old) with the response tagged as using a cached rate, or, if no sufficiently fresh rate exists, declining to complete that specific step and asking the user to retry, rather than inventing a number. Payment processors and banks operationalize exactly this distinction: currency conversion and pricing calculations are typically fail-closed (the transaction is held or declined rather than guessed), while things like an estimated delivery date or a 'customers also viewed' list are fail-open with a generic default, because the cost of being wrong is asymmetric between the two.
- How would you decide whether a given field should 'fail open' with a default or 'fail closed' with an error, when designing a new fallback?Ask what the cost of being silently wrong is versus the cost of the request failing outright. If wrong data can cause financial loss, legal exposure, or safety issues (pricing, exchange rates, compliance flags), fail closed. If wrong data is merely cosmetic or low-stakes (a recommendation, a display count), fail open with a sensible default.
- If you must fail open on a financial field for availability reasons, how do you reduce the risk of the 1.0-exchange-rate scenario?Use the last known real value with a strict freshness bound rather than an arbitrary constant, tag the response so downstream systems and reconciliation know it was a fallback, and set an alert on the fallback-usage rate so it's caught within minutes, not weeks.
- What signal would have caught the 1.0 exchange-rate bug faster than waiting for customer complaints?A metric/log emitted every time the fallback fires, alerting on rate or absolute count, plus a sanity-check assertion (e.g. reject any exchange rate more than X% away from the last known real rate) that would catch an obviously implausible value like 1.0 for a currency pair that's never near parity.
It's like a fuel gauge that, when broken, just always shows 'half tank' instead of an error light — the car still drives fine on the display, but you might run out of gas with zero warning.
saying these in an interview costs you the question
- Treats all default-value fallbacks as equally safe regardless of what the value represents
- Doesn't propose any way to detect that the fallback is firing
- Suggests picking an arbitrary constant for a financial/legal field without discussing fail-closed as an option
- Doesn't distinguish cosmetic fields from correctness-critical ones
- Assumes 'the code still runs without an exception' means the fallback is fine