A nightly import batch arrives with zero rows; what must a traversal over that empty collection return, and why?
answer
- zero rows is not a failure
- no step ran, nothing failed
- vacuously true over no elements
- success holding the empty collection
- identity result, not the absent context
basics
~10 sA traversal over an empty collection returns success holding an empty collection. No step ran, so no step failed; returning the absent context or a failure would report a problem the batch never had.
solid answer
~40 sIt must return the **successful** wrapped value holding an **empty** collection. The traversal's promise is "one wrapped collection of every step's result"; with no elements there are no steps, nothing was attempted and nothing failed, so the claim holds vacuously. Returning the absent or failed context instead invents a failure no row produced, and a nightly job would then alert on a quiet night. It also breaks composition: if you split a batch into chunks, a single empty chunk would poison a run that had nothing wrong with it. Implementations that initialise an accumulator before the loop and return it afterwards get this for free; hand-written loops that decide the outcome from the last element, or treat "accumulator still empty" as failure, are where the bug lives.
code
pseudocode · 7 linesfunction traverseAll(rows, step): // step: row -> wrapped<T>
out = empty list // seeded before the loop
for each row in rows:
r = step(row)
if r is failure: return r
out.append(value of r)
return success(out) // zero rows: loop never runsgo deeper
Remember the answer and say it plainly: an empty batch traverses to a success holding an empty collection, because no step ran and none failed.
Give the reason, not just the answer - a claim about every element is vacuously satisfied when there are no elements - and show the loop shape that gets it right without a branch.
Point at the consequences you have seen: false alerts on a quiet night, and chunked runs that manufacture failures the data never contained.
Decide what a shared import contract says about empty input, including whether downstream notifications fire on a legitimately empty night, and write it down once for every team.
## The case nobody tests A meter import runs every night and has seen every kind of bad row, but it has never seen a file with no rows. Then a holiday, an outage upstream or a filter that matched nothing delivers one. The question the traversal has to answer is narrow and exact: when a wrapped step is run over **no** elements, what comes back? ## The answer and the reason The answer is the **successful context holding the empty collection**. The reasoning is worth being able to say out loud, because interviewers ask precisely to hear it: - The traversal's contract is "apply the step to every element and give me one wrapped collection of the results". - Over an empty collection, the step runs zero times. Nothing was attempted, so nothing was attempted and failed. - A statement about *every* element of an empty collection is vacuously satisfied - there is no element that violates it. - Therefore the traversal has succeeded, and the collection it succeeded with has no elements in it. ## The three wrong answers 1. **The absent or empty context.** This reports "no value" when the honest report is "a value, and it happens to contain nothing". Every downstream caller now treats a quiet night as an anomaly. 2. **A failure.** This invents a failure that no step produced. The failure channel is meant to carry what a step reported; with no steps, it has nothing to carry. 3. **A third case** - a sentinel, a flag, a special marker for "empty batch". This pushes a case onto every caller that the type system had already made unnecessary, and the cases multiply as the code grows. ## Batch to result, in one table | batch | fail-fast traversal returns | |---|---| | zero rows | success holding an empty collection | | every row valid | success holding one value per row, in order | | one row invalid | that row's failure, carrying no values | | all rows invalid | the first row's failure under fail-fast | ## Why the identity answer is the one that composes The empty result is the **identity** of the traversal: the value that combining nothing yields, and that combining with anything changes nothing. The same reasoning fixes the sum of no numbers at zero and the product of none at one. Two practical consequences follow: - **Chunking stays safe.** Split a million-row batch into chunks and traverse each: an empty chunk contributes an empty collection and the run is unaffected. If empty meant failure, the split itself would introduce failures the data never had. - **Concatenation stays honest.** Appending the results of two traversals, one of which was empty, gives exactly the other one's results. Any other answer for the empty case makes that appending lie. ## Does the wrapper change the answer? No. Whether each row's step returns a value-or-absent context or a value-or-described-failure context, the empty traversal returns the successful side of that context holding an empty collection. Only the wrapper differs; the reasoning about zero steps does not. ## Getting it right in code, and proving it - **Initialise the accumulator before the loop and return it after.** Written that way, the empty case needs no branch at all - the loop simply does not execute and the seeded accumulator is returned. - **Do not derive the outcome from the last element.** A loop whose result variable is assigned inside the body has nothing to return when the body never runs, which is how the absent-context answer sneaks in. - **Do not read "accumulator is still empty" as failure.** That conflates "nothing was produced" with "something went wrong". - **Test three inputs, always:** zero rows, one good row, one bad row among good ones. The first of those is the one that reaches production untested. - **Watch the caller, too.** A caller that interprets an empty successful collection as "nothing to do, skip the downstream notification" may be right or wrong, but that is a product decision made at the caller - not a reason to change what the traversal returns. - **Keep the alerting where it belongs.** If a zero-row night genuinely is suspicious for this feed, the check that says so is a separate rule about expected volume, sitting beside the traversal and reading its successful empty result. Folding that judgment into the traversal makes the operation dishonest for every other caller, and the rule becomes impossible to tune without changing the import itself.
- Why does the empty-batch bug survive so long before anyone notices it?Because a nightly batch is almost never empty. The first empty file usually arrives from an upstream outage or a holiday, so the defect surfaces during an incident, on top of whatever caused the empty batch, and the false failure is easy to mistake for the real problem.
- Does the answer differ when each row's step returns a described failure rather than a bare absence?No. In both cases the traversal returns the successful side of the context holding an empty collection. The wrapper differs; the reasoning does not, because zero steps means zero failures of either kind.
- How does the empty case interact with splitting a batch into chunks?It is what makes splitting safe. An empty chunk contributes an empty collection and leaves the overall outcome unchanged. If an empty traversal failed, chunking a perfectly good batch could manufacture a failure that the data never contained.
saying these in an interview costs you the question
- Says an empty batch should return the absent or empty context
- Treats zero rows as a failure the nightly job must report
- Thinks the answer depends on what the rows would have contained
- Assumes an empty batch can never reach the traversal
- Claims a traversal needs at least one step to have a result