skip to content

You make one parameter of a heavily-called check non-strict; what must you verify about the arguments callers already pass?

level: seniorimportance: should knowfreq 38%

answer

  1. the call sites do not change
  2. who evaluates it, and when
  3. effects move to the demand point
  4. failures surface inside the callee
  5. a cached read samples first demand

basics

~10 s

Verify that existing argument expressions tolerate being evaluated later, elsewhere, possibly never, and possibly more than once: their effects, their failure timing, when they sample mutable state, and any reliance on left-to-right argument order.

solid answer

~40 s

The call syntax does not change, which is what makes this quiet and risky. What changes is who evaluates the argument and when: evaluation moves from the caller into the callee, may not happen at all, happens at the body's demand point, and under a re-evaluating strategy can happen once per use. So check four things in the arguments callers already write. Any observable effect inside an argument expression now fires conditionally rather than on every call. A failure while building the argument now surfaces inside the callee, past whatever handler wrapped the call. An expression that reads mutable state is sampled at first demand, not at the call. And with several deferred arguments, the body's demand order replaces argument order.

code

pseudocode · 8 lines
pseudocode
// before: description is evaluated at every call site
function check(enabled, description)

// after: description is evaluated only where the body uses it
function check(enabled, non_strict description)

check(level_enabled, record_count_and_format(batch))
// the count is now recorded only when level_enabled is true

go deeper

for a junior

The point to carry away is that a call can look exactly the same and mean something different, because the argument is now evaluated inside the function rather than before it.

for a middle

Be able to list what moves: effects become conditional, failures surface at the demand point, a read of mutable state is sampled later, and argument order stops governing evaluation order.

for a senior

Show how you would audit existing callers rather than only reasoning about new ones, and explain how you would decide the change is worth it — what share of calls ignore the parameter, and how expensive the expression is.

for a principal

The trade you own is legibility against cost: deferral makes the expensive path free but makes every call site harder to read, since the call no longer tells a reviewer what was evaluated or when.

## What actually moves Making a parameter non-strict usually leaves the call sites textually unchanged — same name, same arguments, same shape. That is exactly why the change is dangerous: nothing at the call site signals that its meaning moved. What moved is **who evaluates the argument expression, and when**: 1. Evaluation moves from the caller's execution into the callee's. 2. It may not happen at all, if the body does not demand the parameter. 3. It happens at the body's demand point, which is some arbitrary line inside someone else's code. 4. Under a re-evaluating strategy it can happen once per use; under a caching one it happens exactly once, at the first demand. Everything a caller previously relied on that touched timing, effects or failure is now suspect. ## The four things to verify 1. **Effects inside argument expressions.** An argument that increments a metric, writes a record, consumes from a source or mutates a structure used to do so on every call. Now it fires only when the body reaches the use — conditionally, and possibly repeatedly. Audit the argument expressions that already exist, not just the ones you imagine. 2. **Failure site and timing.** An expression that can fail used to fail at the call site, inside whatever handler surrounded the call, with the caller's context available. Now it fails inside the callee at the demand point, where a different handler — or none — is in scope, and the reported context describes the callee's work rather than the caller's. 3. **Sampling of mutable state.** An expression reading a field, a clock or a shared structure used to capture its value at the instant of the call. With caching it captures the value at the first demand; with re-evaluation it captures a fresh value at each use. A caller that assumed the call-site instant is now reading something else. 4. **Order between arguments.** If two arguments both became non-strict, the order their expressions run in is decided by the order the body demands them, which is an implementation detail of the callee and may change when the callee is refactored. Any caller relying on left-to-right effects at the call site loses that guarantee. | A caller assumed… | After the change | |---|---| | The argument's effect happens on every call | It happens only if the body uses the parameter | | A failure while building it surfaces at the call | It surfaces inside the callee, at the demand point | | The value reflects state at the moment of the call | It reflects state at first demand, or at each use | | Arguments evaluate left to right | The body's demand order decides | ## The cost you are trading The gain is concentrated on the path where the argument is ignored: the expensive expression is never evaluated, which for a heavily-called check with a rarely-enabled branch can be most of its total cost. The price is paid on the path where it is used — the machinery of deferral and, under a caching strategy, of recording whether the result exists yet — plus a cost model that is now harder to read at the call site, since the call no longer tells you what it evaluated. There is also a context question. The expression is evaluated wherever the demand occurs, which may be a different execution context from the caller's. Anything in the expression that depended on the caller's surrounding context should be read at the call site and passed as an ordinary value rather than left inside the deferred expression. ## When this change is wrong If the parameter is used on essentially every call and the expression is cheap, deferral buys nothing and costs the machinery plus all four hazards above. The change earns its keep only when a meaningful share of calls never demand the parameter and the expression is expensive enough to notice. Otherwise the honest options are to leave the parameter strict, or to have callers pass an already-computed value. ## What interviewers listen for They want to hear that you treat this as a change to the **contract**, not to an implementation detail, even though no signature that callers read has to change. The strong answer enumerates effects, failure relocation, sampling and ordering, and then says how it would find the affected call sites rather than reasoning only about new ones. A weak answer treats it as a pure optimisation because the arguments "look the same".

  • With two deferred arguments, what decides the order their expressions run in?
    The body's demand order, not the order they appear in the argument list. Whichever parameter the body reaches first is evaluated first, and a later refactor inside the callee can swap them without touching any caller. Any caller that relied on left-to-right effects at the call site has lost that guarantee silently.
  • How would you find the call sites this change actually affects?
    Look for argument expressions that do something rather than just read a value: ones with observable effects, ones that can fail, ones reading mutable state, and ones whose sibling argument also became non-strict. Arguments that are plain values or pure computations over immutable data are unaffected, so the audit is usually much smaller than the call count suggests.

saying these in an interview costs you the question

  • Says deferring a parameter cannot change where a failure surfaces
  • Assumes argument effects still fire in call-site order
  • Believes a cached deferred read reflects the call-site instant
  • Treats deferral as free on the path where the argument is used
  • Thinks the change is invisible because call sites look identical