skip to content

A delivery-fee function reads the clock for a cut-off, bumps a metrics counter and writes a log line — which of those make it impure?

level: juniorimportance: must knowfreq 84%

answer

  1. two halves, not one
  2. inputs in, changes out
  3. hidden inputs count as effects
  4. the clock is an undeclared parameter
  5. counter and log are outward writes

basics

~20 s

A clock read, a counter bump and a log write are all side effects. Purity needs two things at once: the result depends only on the arguments, and the call changes nothing observable outside itself.

solid answer

~40 s

All three. Purity has an **input** half and an **output** half. The input half says the result is determined by the arguments alone; reading the clock breaks it, because the same order quoted twice around the cut-off yields two different fees while the arguments never changed. The output half says the call leaves nothing observable different; the counter and the log line both break it, because state and a stream outside the call are different afterwards. The counter and log do not change the returned fee, and that is exactly the trap — an effect does not have to feed back into the result to be an effect. The local total the function accumulates while summing line items is not an effect: it is created, mutated and discarded inside the call.

code

pseudocode · 10 lines
pseudocode
function deliveryFee(order, rateCard)
    weight = 0                                 // benign: local, discarded at return
    for each line in order.lines
        weight = weight + line.weight * line.quantity
    fee = rateCard.base + rateCard.perKilo * weight
    if now() after rateCard.cutOff             // hidden input: nothing declared it
        fee = fee + rateCard.lateSurcharge
    quotesIssued = quotesIssued + 1            // outward write: shared counter
    log("quoted " + fee + " for " + order.id)  // outward write: a stream
    return fee

go deeper

for a junior

Be able to state both halves of purity without prompting: same arguments give the same result, and the call changes nothing outside itself. Then classify a short function's lines against them.

for a middle

Explain why an effect that never touches the return value is still an effect, and why a hidden read is an undeclared parameter. Separate deterministic from pure out loud.

for a senior

Show you audit by inventory rather than by intuition: name every undeclared read and every outward write in a function, including the ones the team has agreed to tolerate, and say what each one costs a caller.

for a principal

The judgement is where the team draws the tolerated-effect line and how it is recorded, so that a function everyone treats as effect-free does not quietly acquire a clock read two refactors later.

## Purity has two halves A function is **pure** when two things are true at the same time: 1. **Its result is determined by its arguments alone.** The same inputs produce the same output, for every caller, every time. 2. **Calling it changes nothing observable.** After the call returns, no state that any other code can reach differs from before. The first half is about what flows **in**, the second about what flows **out**. A **side effect** is a violation of either half. An *input effect* consults something the parameter list never declared; an *output effect* writes something that outlives the call. Most candidates remember only the second half and audit only for writes, which is precisely why a clock read walks through review untouched. ## Auditing the delivery-fee calculator | Step in the calculator | Which half it breaks | How you would notice | |---|---|---| | Reads the current time to test the next-day cut-off | input: an undeclared input | the same order quoted twice across the cut-off gives two fees | | Increments a shared counter of quotes issued | output: an outward write | a value outside the call is higher afterwards; the fee is unchanged | | Writes one line to the application log | output: an outward write | a stream outside the call received data | | Draws from a random source to pick an experiment bucket | input: an undeclared input | identical calls disagree with no clock involved | | Sums the line items into a local total | neither | nothing outside the call is read or written | Four of the five entries are effects. The audit does not rank them by how much anyone cares: a log line and a write to a permanent store are the same **kind** of thing, and they differ only in consequence. ## The entries candidates miss are the reads An outward write usually announces itself, because it names a sink — a log, a counter, a store, a stream. A hidden input looks like an ordinary expression in the middle of arithmetic, so it never triggers the reviewer's alarm. The recurring ones: - the current time or date - a random source, or a generator of fresh identifiers - ambient configuration, an environment value, or a feature flag consulted inside the body - a value another call cached on shared state earlier - an implicit "current user" or "current request" looked up rather than passed Every one of these is an argument the function genuinely needs and never declared. That is the useful way to say it in an interview: a hidden input is a **parameter in disguise**, and the signature is lying about what the function depends on. ## What "observable" actually means Observable means *observable to somebody else*. The test is whether any other code — including a later call to this same function — can tell that the call happened. That gives two symmetric mistakes, and interviewers probe both: - **"It does not change the result, so it is pure."** Wrong. The counter bump and the log write never touch the fee, and both are effects, because the world outside the call is different afterwards. - **"There is an assignment in the body, so it is impure."** Also wrong. Purity forbids observable change, not assignment. A local variable created, mutated and dropped inside one call cannot be observed by anyone. A related distinction worth having ready: **deterministic** and **pure** are not the same word. A function that bumps a counter and returns the same fee for the same order is deterministic and impure. A function that reads the clock and writes nothing is nondeterministic and, by the same token, impure. Purity requires both halves. ## Running the audit in review 1. List every name the body reads that is neither a parameter nor a local it created. 2. List every write whose target is reachable from outside the call — a field on an argument, shared state, a stream, a store. 3. For each entry, ask whether any other code, or a second call, can tell the difference. 4. Record a throw separately: it is a second exit the return type does not advertise, and it deserves its own verdict. Whatever survives those four steps is the honest effect inventory for the function. Teams often decide to tolerate some of it — logging and metrics are routinely accepted as the price of operating a service — but tolerating an effect is a decision about consequences, not a reclassification. The function is still impure, and anything that assumed otherwise about it is still wrong.

  • Which of these effects can two consecutive calls with identical arguments reveal on their own?
    Only the input effects. Calling twice with the same order shows the clock read, because the two fees can differ. The counter bump and the log write leave both fees identical, so you find them by looking at the world around the call, not at its result.
  • Why is a hidden input usually harder to spot in review than an outward write?
    An outward write names its destination — a counter, a log, a store — so it reads as an action. A hidden input reads as an ordinary value in an expression, indistinguishable at a glance from arithmetic over the parameters. The signature gives no clue either, because the dependency was never declared.
  • The team decides the log line is acceptable. Does the function become pure?
    No. Accepting an effect is a judgement about its consequences, not a change in its classification. The function still writes something a reader outside the call can see, so anything that relies on the function being effect-free is still relying on something untrue.

saying these in an interview costs you the question

  • Thinks only writing to a database or a file counts as an effect
  • Calls the clock read harmless because it only reads and returns a value
  • Says logging cannot break purity because nobody reads the log
  • Assumes a function that returns a value and takes no parameters is pure
  • Treats every assignment inside the body as proof of impurity
  • Uses deterministic and pure as interchangeable words