skip to content

A transfer request is validated for amount, currency and destination account — what changes when the response must list every bad field?

level: middleimportance: must knowfreq 58%

answer

  1. three checks, two ways to join
  2. a failed step feeds nothing forward
  3. independent parts can all be run
  4. two failures need a merge rule
  5. dependence forces stopping at the first

basics

~20 s

The three checks stop being a chain and become independent parts combined together. A chain abandons the remaining checks at the first failure; combining runs all three and merges their failures into one description the response can render.

solid answer

~40 s

Chaining is the default shape: each step consumes the previous step's success value, so a failed step has nothing to hand on and the steps after it never execute — one failure comes back and the later checks were never performed. To name every bad field, the three checks must be independent, each reading the original request, and their outcomes combined by a rule that knows how to merge two failures into one. That merge rule is the extra requirement; in practice the failure carries a collection of field-level descriptions and merging is concatenation. The trade-off is real: accumulation is only available where the checks do not depend on one another, and a step that needs an earlier step's value must still short-circuit.

code

pseudocode · 7 lines
pseudocode
# dependent chain: each step consumes the previous success value
parseAmount(request)
  .then(amount -> priceIn(amount, request.currency))
  .then(priced -> chargeable(priced, request.destination))

# a failure in parseAmount returns immediately;
# the two later steps are never executed

go deeper

for a junior

Know the two reports: one failure and the later checks unrun, or every failure gathered together. Be able to say which one a form on a screen needs.

for a middle

Explain the mechanics: a chain has no value to feed the next step, so it stops; combining runs independent checks and merges their failures, which is why the failure type carries a collection.

for a senior

Show that you weigh cost and effects, not just the report. Say which checks you would gate behind a cheap pass and how you would describe a field that could not be checked.

for a principal

Set the house rule: where the boundary between the accumulating pass and the dependent pipeline sits, and what shape of failure description every service publishes so responses stay uniform.

## Two shapes three checks can take Three checks on one transfer request — the amount, the currency, the destination account — can be assembled in two useful ways, and the choice decides what the caller can be told. Neither shape is more "functional" than the other; they answer different questions, and the question is what the response has to say. **A dependent chain.** Each step takes the previous step's success value and produces the next. Because a failed step has no success value to hand on, the steps after it are not executed at all: with three checks and a failure in the first, the other two never run, and exactly one description comes back. ``` # each step consumes what the previous step produced parseAmount(request) .then(amount -> priceIn(amount, request.currency)) .then(priced -> chargeable(priced, request.destination)) # a failure in parseAmount means the last two steps never execute ``` **Independent parts combined.** Each check reads the original request and knows nothing about the others. A combining step then looks at all three outcomes together: if all three succeeded it hands their values to a builder, and if any failed it merges every failure present into one. ``` a = checkAmount(request) # Success(amount) or Failure(["amount must be positive"]) c = checkCurrency(request) # Success(currency) or Failure(["currency not supported"]) d = checkAccount(request) # Success(account) or Failure(["no such destination account"]) combine(a, c, d, (amount, currency, account) -> Transfer(amount, currency, account)) # all three ran; whichever failures occurred are joined into one ``` ## What the failure side has to provide Combining only works if two failures can become one failure. That is a requirement the short-circuiting shape never has, because it is never holding two failures at once. Concretely: - the failure carries a **collection** of descriptions rather than a single one, so merging is concatenation; - each description says **which field** it belongs to, or the response cannot put the message beside the right input; - merging must be **associative** — combining three outcomes gives the same result whichever pair you join first — otherwise the response depends on evaluation order; - the success case still produces **one** value, so the builder runs only when all three checks succeeded; - there must be an answer for **zero failures**: that case is the success, not an empty failure. ## Which shape is right when | | dependent chain | independent parts | |---|---|---| | later steps use earlier values | required | impossible | | checks executed on a bad request | stops at the first failure | all of them | | failures reported | exactly one | every one that occurred | | typical setting | a pipeline of transformations | a form or request payload | | a step with a cost or an effect | later work is avoided | later work still happens | The last row is the one candidates forget. Short-circuiting is not only about the report; it is also about *not doing the remaining work*. If checking the destination account costs a directory lookup, accumulating pays for that lookup even when the amount was already nonsense — which is usually acceptable for a request payload and usually not for a long pipeline. ## The dependency that forces short-circuit Accumulation is available only where the checks are genuinely independent, and that is a property of the code, not a preference. Suppose the destination check does not inspect a string but loads the account named by an identifier that an earlier step parsed. When parsing failed, that check has no input at all, so its verdict is *unknown* rather than absent, and the honest report is "amount invalid; destination not checked". Mixed shapes are the normal outcome and a good answer says so: 1. Parse and validate the independent fields, accumulating every failure among them. 2. Only if all of them succeeded, chain the steps that need those parsed values. 3. Report the accumulated failures when stage one fails, and the single failure when stage two does. That gives a user everything that can be known in one pass without pretending to have checked things that could not be checked. ## What interviewers listen for - That you name **dependency between the checks** as the deciding factor, not personal style. - That you notice the failure side needs a merge rule at all, and can say what it would be. - That you can describe, in one sentence each, the report the user sees under both shapes. - That you do not claim the choice is free: accumulation runs work that short-circuiting skips.

  • Why can a check that needs an earlier check's value not accumulate?
    Because it has no input when the earlier check failed — there is no parsed identifier to look up. Its verdict is unknown rather than absent, so the honest report says the field was not checked, and the shape has to short-circuit at that point.
  • What must the failure side support for accumulation to work?
    A rule for merging two failures into one, so three outcomes reduce to a single failure carrying every description. A collection of field-tagged descriptions is the usual choice; a lone message string forces you to invent a join and loses which field each message belongs to.
  • Is accumulating ever the wrong default for a request payload?
    Yes, when a check is expensive or has an effect. Accumulation runs every check, so a directory lookup or a remote call happens even for a request already known to be invalid. Cheap, local field checks accumulate; costly ones sit behind a gate.

saying these in an interview costs you the question

  • Thinks the accumulating shape still stops at the first bad field
  • Says any set of checks can be made to accumulate, dependencies included
  • Forgets the failure side needs a rule for merging two descriptions
  • Believes short-circuiting runs the later checks and throws their results away
  • Returns one field error at a time to a user filling a form
  • Ignores that accumulation pays for checks a chain would have skipped