Folding a very long transaction log, what does a left fold do differently from a right fold?
answer
- bracketing, not visiting order
- the seed sits at the opposite end
- (accumulator, element) versus (element, accumulator)
- same answer only when associative
- left loops, right can emit early
basics
~20 sThey bracket the combinations from opposite ends, so a non-associative step gives different answers. A left fold runs as one loop over a running accumulator; a right fold must reach the far end before its outermost combination can finish, unless evaluation is non-strict.
solid answer
~50 sA left fold brackets from the front - `((seed + t1) + t2) + t3` - and its step takes `(accumulator, element)`. A right fold brackets from the back - `t1 + (t2 + (t3 + seed))` - and its step takes `(element, accumulator)`. When the step is associative and the seed is its identity, both produce the same value; when it is not, they differ, and subtraction over the same three numbers is the standard demonstration. On a very long log the practical difference is cost. A left fold is a loop: one accumulator, a single forward pass, constant extra space - but it produces nothing until the last element has been consumed. A right fold under eager evaluation has to descend to the end before any combination completes, holding one pending combination per element. Under non-strict evaluation with a step that does not force its accumulator argument, that same right fold can emit the front of its result immediately and stop early.
code
pseudocode · 11 lines// Left: seed on the outside, grouping from the front
// ((0 - 1) - 2) - 3
foldLeft([1, 2, 3], 0, subtract) // -6
// Right: seed on the inside, grouping from the back
// 1 - (2 - (3 - 0))
foldRight([1, 2, 3], 0, subtract) // 2
// Associative step with its identity: both agree
foldLeft([1, 2, 3], 0, add) // 6
foldRight([1, 2, 3], 0, add) // 6go deeper
Know that the two directions bracket the combinations from opposite ends, and that for plain addition over a list of amounts the answer comes out the same.
Write both bracketings out for three elements, produce a non-associative step where the results differ, and state which argument of the step is the accumulator in each direction.
Explain what each direction does to a log of a million lines - the flat single-accumulator pass against the chain of pending combinations - and say which one you would put in a batch job reading a stream.
Treat direction as part of a calculation's contract when the step is not associative: it belongs in the specification and in a test that fails under the other direction, not in one engineer's memory.
## The bracketing is the difference Write both out for three transactions and the whole question answers itself. With `(+)` standing for any combining step and `z` for the seed: - **left fold**: `((z + t1) + t2) + t3` - the seed is on the **outside left**, and the step is applied as `(accumulator, element)`. - **right fold**: `t1 + (t2 + (t3 + z))` - the seed is on the **inside right**, and the step is applied as `(element, accumulator)`. Two things follow immediately: the argument order of the combining step is swapped, and the grouping runs the other way. Everything else is a consequence of those two. ## When the answers differ If the step is **associative** and the seed is its **identity**, the two folds agree - the grouping cannot be observed. Adding amounts, appending collections and taking the maximum all behave that way. If the step is not associative, they disagree, and the disagreement is not subtle: ```pseudocode fold_left([1, 2, 3], 0, subtract) // ((0 - 1) - 2) - 3 = -6 fold_right([1, 2, 3], 0, subtract) // 1 - (2 - (3 - 0)) = 2 ``` Subtraction, division, averaging a running value with each element, and "keep the newer record on conflict" are all non-associative, and for all of them the direction is part of the specification rather than a stylistic choice. ## What each one costs on a long log | | Left fold | Right fold | |---|---|---| | Step's argument order | `(accumulator, element)` | `(element, accumulator)` | | Natural machine shape | a loop over one accumulator | descend to the end, combine on the way back | | Pending work at peak | one accumulator | one pending combination per element, under eager evaluation | | First output available | only after the last element | possibly after the first element, under non-strict evaluation | | Suits | collapsing a long or streamed log to one value | building a structure lazily, or stopping early | The left fold's shape is the one every imperative loop already has: read an element, update the accumulator, discard the element. Memory does not grow with the log's length, which is why it is the direction that survives a million lines. The right fold's outermost combination is `t1 + <everything after t1>`, so the value on its right-hand side is not known until the end of the log has been reached. Under eager evaluation, where every argument is computed before the step runs, that means the whole chain of pending combinations is outstanding at once and the depth grows with the log. This is the origin of the "it worked in testing and blew up on the production file" story. ## Why a right fold is not simply worse Evaluation strategy changes the picture, and languages differ here: most compute arguments eagerly, some defer them until demanded. If the step **does not force its second argument** - for example it builds a pair whose tail is the pending combination - a right fold can hand back the first piece of its result after seeing one element, and the rest is computed only if someone asks. Two capabilities follow that a left fold does not have: 1. **Early exit.** A step that ignores its accumulator argument on some element ends the fold there, having touched only a prefix of the log. 2. **Unbounded input.** A feed with no end can still be folded, because the fold never needs to reach an end it does not have. A left fold, as normally defined, visits every element before producing anything, so neither of those is available to it. ## Choosing a direction - Collapsing a long or streaming log to a **single accumulated value**: fold left. - Building a **structure** where the front of the result is wanted before the back is computed, or where the consumer may stop early: fold right, if the evaluation strategy supports it. - The step is **not associative**: the direction is dictated by the answer you want, and should be stated explicitly in the code and in the test. - The step **is** associative with an identity seed: pick on cost, because the value is the same either way - and that same associativity is what later lets the work be split across workers. ## What an interviewer is listening for Most candidates say "one goes left to right and the other right to left", which is the visiting order and not the point. The answer that lands names the **bracketing**, produces a non-associative example where the two results differ, and then says what each direction does to a log big enough to matter.
- Which direction would you choose for reconciling a log too large to hold in memory, and why?Left. It keeps exactly one accumulator and consumes the log in a single forward pass, so memory is flat in the number of lines and the stream can be read and discarded as it goes. A right fold needs the far end of the log before its outermost combination can complete.
- The team folds with subtraction and the two directions disagree - which one is the bug?Neither, in isolation: the step is not associative, so the direction is part of the specification. The bug is that the intended grouping was never written down, and the fix is to state which direction the calculation means and pin it with a test that would fail under the other.
- Can a fold stop before the end of the log?A right fold can, under non-strict evaluation, if the combining step declines to force its accumulator argument for some element - the rest of the log is then never demanded. A plain left fold cannot, because its result is only available after the final combination.
saying these in an interview costs you the question
- Says the two directions always produce the same value
- Describes the difference as only the order elements are visited
- Expects a right fold over a huge log to use constant stack
- Cannot say which argument of the step is the accumulator
- Thinks a subtracting fold totals the same either way
- Believes the seed sits at the same end in both directions