In a linear search, why is a zero-valued record a bad way to signal 'not found'?
answer
- count the outcomes a caller must distinguish
- empty collection versus a genuine miss
- a stored zero is a real value
- keep the found bit out of band
- which of several matches comes back
basics
~20 sA zero-valued record conflates three different outcomes: the entry is missing, the collection was empty, and the entry exists but its value happens to be zero or disabled. Callers cannot tell them apart, so a failed load silently reads as 'everything off'.
solid answer
~50 sA search has two independent pieces of output — *was anything found* and *what was found* — and encoding both into one value destroys the first. If a flag lookup returns a zeroed record when nothing matched, the caller branching on that record's enabled field sees exactly the same thing whether the flag is absent, the flag list is empty, or the flag genuinely exists and is switched off. The classic production incident is a config load that silently failed: the scan finds nothing, every lookup reports "off", and the system quietly takes default paths for hours. The fix is to keep the found signal **out of band** — return a position and reserve an out-of-range index for a miss, or return an explicit present/absent result that the caller must unwrap before touching a value. Also document which match wins: a scan returns the *first* match by construction, and callers depend on that whether or not you wrote it down.
code
pseudocode · 12 linesFIND-FLAG(flags, name)
for i in 0..length(flags)-1
if flags[i].name == name
return flags[i]
return ZERO_RECORD // enabled == false
// caller
f = FIND-FLAG(flags, "beta_checkout")
if f.enabled
take_new_path()
else
take_old_path() // absent? empty list? really off?go deeper
Be ready to say that a search reports two things — whether something was found and what it was — and that squeezing both into one value hides misses. Name one safe alternative.
Explain concretely which outcomes collapse together when a default value doubles as the miss signal, and describe an out-of-band alternative such as an out-of-range position or an explicit present/absent result.
Show you have seen the failure mode: a silent config miss reading as healthy defaults. Talk about where the empty-versus-missing distinction actually belongs and how you would make the bad state loud.
Own it as an interface convention across a codebase: one agreed not-found signal, documented duplicate semantics, and a review rule that rejects any miss value a real element could equal.
## Two outputs pretending to be one Every search answers two questions at once: **did anything match**, and **which element was it**. These are independent. The moment you encode the first into the value space of the second, you have created an ambiguity that the type system cannot help you with and that reviewers must catch by eye. Returning a zero-valued (or empty, or default-constructed) record for "not found" is the most common form of this mistake, because it is so convenient — the caller gets something shaped like a record and can dot into it without checking anything. ## The three states that collapse into one Walk a feature-flag lookup that scans a list of flag records for a matching name and returns a zeroed record when the scan falls off the end: 1. **The flag is absent from a populated list.** Someone typo'd the name, or the flag was retired. Expected, benign, worth a warning. 2. **The list itself is empty.** The config never loaded, or loaded into the wrong place. This is an operational failure, and it is the state you most want to be loud about. 3. **The flag is present and switched off.** Entirely normal, and the whole point of having flags. All three return a record whose enabled field is false. The caller writes the obvious branch, takes the old code path, and nothing anywhere logs a problem. State 2 — the outage — is indistinguishable from state 3 — the healthy steady state. That is why this is a bug and not a style preference: the failure mode is *silence*. ## What a good contract looks like There are three defensible shapes, and the choice is about the calling code, not about taste. **Return a position, reserve an impossible one for a miss.** A scan that yields an index can signal absence with an index outside the valid range — most conventionally, one below the first valid index. It is cheap, it composes with subsequent indexing, and the sentinel is genuinely impossible to confuse with a real position. Its weakness is that nothing forces the caller to check; the discipline lives in review. **Return an explicit present/absent result.** A wrapper the caller must unwrap before reaching a value moves the check from convention to structure — forgetting it becomes a compile-time or at least an obvious runtime event rather than a silent default. This is the modern default where the language offers it, and the reason it is worth the extra type. **Return the value plus a separate found indicator.** Two outputs, honestly separated. It works, and it is what several mainstream runtimes settled on — Go's comma-ok lookups and C++'s end-iterator convention are two different answers to the same design question, and both keep the "was it found" bit strictly outside the value. What is *not* defensible for an ordinary lookup is raising an error. A miss is a normal, expected outcome of asking whether something is present; reserving exceptions for it makes routine control flow expensive and noisy, and pushes callers toward swallowing the error broadly. ## The empty-collection question Does scanning an empty collection deserve a different result from a genuine miss? At the level of the search itself: no. Zero elements examined, nothing matched, that is a miss and the contract should say so plainly — no special case, no error. Special-casing empty input inside the scan is a common over-correction that adds a branch and answers a question the caller did not ask. But the layer above almost always cares. "Configuration was never loaded" and "configuration was loaded and this key is absent" are different operational facts, and the right place to distinguish them is at load time — a loaded-successfully flag, a startup assertion, a metric on collection size — not inside a search that has no idea how the collection came to be empty. ## Which match wins One more clause of the contract, usually left implicit: a sequential scan returns the **first** match in iteration order. When duplicates are possible, callers depend on that fact whether or not it was ever documented. Reversing the iteration for a micro-optimization, splitting the scan across workers, or switching to a structure with a different traversal order will silently change which record comes back. If first-match-wins is guaranteed, write it down; if it is not, say the result is unspecified among duplicates and make sure that is actually true. ## Reviewing for it The review heuristic is short. Look at the value the search returns on a miss and ask: *could a legitimately stored element ever be equal to this?* If yes — zero, empty string, empty record, false — the contract is ambiguous and the ambiguity will surface as an outage that looks like normal operation.
- Should scanning an empty collection produce a different result from a genuine miss?Not inside the search — zero elements examined, nothing matched, that is an ordinary miss and special-casing it adds a branch for no caller. The distinction that matters, 'config never loaded' versus 'loaded and this key is absent', belongs at load time: a success flag, a startup assertion, or a metric on collection size.
- If several records match, which one does a linear scan return, and does it matter?The first one in iteration order, by construction. Callers routinely rely on that without it ever being documented, so a reversed loop, a split-across-workers scan, or a change of traversal order silently changes behaviour. Either guarantee first-match-wins in writing, or state that the result is unspecified among duplicates and make sure that is true.
- Why not just raise an error when the scan finds nothing?Because a miss is a normal, expected answer to 'is this present?', not a failure. Errors on the ordinary path make routine control flow expensive and noisy, and they push callers into broad catch-everything blocks that swallow real faults alongside the misses. Reserve errors for states the caller cannot reasonably be asked to handle inline.
saying these in an interview costs you the question
- Uses a zero or empty value to signal not found
- Cannot say what an empty collection returns
- Raises an error for an ordinary missing key
- Leaves first-match-wins undocumented for duplicates
- Assumes every caller remembers to test before using the value