Beyond nesting, what specific code properties increase the reader's short-term memory load, and what techniques reduce it?
answer
- WM ≈ 4 chunks (Cowan), not 7±2
- chunking = replace N items with 1 good name
- live range, not variable count
- flag params & hidden side effects = invisible items to hold
- extraction backfires when the name is not an abstraction
basics
~20 sAnything the reader must hold in their head while reading on: long-lived mutable variables, boolean flag parameters, unnamed intermediate conditions, hidden side effects, and jumping between distant files. Fix by naming intermediates, shrinking variable lifetimes, and making each named function a single chunk.
solid answer
~50 sHuman working memory holds only a few independent items (Miller's 7±2, revised to about 4 chunks by Cowan). Code taxes it whenever the reader must *remember something to understand a later line*: a mutable variable assigned far from its use, an enclosing condition, a boolean parameter whose meaning is at the callee's declaration, an out-parameter, an implicit side effect, or a call chain that forces jumping between files ('yo-yo' reading). The countermeasures all work by **chunking** — replacing several items with one named item that long-term memory can supply: extract a well-named function or intermediate boolean (`isEligibleForRefund = …`), keep a variable's declaration adjacent to its use and its live range short, prefer immutable values, replace flag parameters with two explicit functions, and return values instead of mutating shared state. Crucially, extraction only helps when the name is a true abstraction; a badly named helper adds a jump without removing an item.
go deeper
Name a few concrete offenders — long functions, unclear names, flag parameters — and say that naming intermediate values helps.
Bring in working-memory limits and chunking, and give three or four concrete techniques with examples.
Cover live ranges, mutation, hidden side effects and non-local state; explain when extraction backfires and why linters cannot see most of this.
Frame it as a systemic property: consistent domain vocabulary, boundary validation and types that make illegal states unrepresentable remove whole classes of facts readers must hold, across the codebase rather than per function.
## The cognitive model Two stores matter when reading code. - **Long-term memory (LTM)** — effectively unlimited, holds what you already know: language syntax, idioms, familiar patterns, your codebase's vocabulary. - **Working memory (WM)** — tiny and volatile, holds what you are juggling right now. George Miller's 1956 figure was 7±2 items; Nelson Cowan's later work puts the practical limit near **4 chunks**. A *chunk* is however much LTM can supply as one unit. `for (i = 0; i < n; i++)` is one chunk to an experienced reader and five to a novice. This is why readability is partly relative to the reader — but the load a piece of code *imposes* is still largely a property of the code. Code becomes hard exactly when it forces you to hold more than a few live items. Nesting is the famous case, but far from the only one. ## What consumes working-memory slots 1. **Enclosing conditions** — every level of nesting is a fact you must carry to the innermost line. 2. **Long variable live ranges** — a variable assigned at line 10 and used at line 90 must be tracked for 80 lines. The **live range** (distance between declaration/last assignment and use) is the real cost, not the variable count. 3. **Mutation and reassignment** — an immutable value is one item; a variable reassigned three times is a value *plus* a timeline of when it changed. 4. **Unnamed intermediate conditions** — `if (u.age > 17 && u.country in allowed && !u.banned && u.verified)` makes you compute and hold the *meaning*. `if (isEligibleCustomer(u))` costs one chunk. 5. **Boolean flag parameters** — `render(doc, true, false)` forces a jump to the declaration to decode the booleans, and each is an item to hold on the way back. 6. **Out-parameters and hidden side effects** — if `validate(order)` also mutates `order`, the reader must remember an invisible fact for the rest of the function. 7. **Non-local / 'action at a distance' state** — globals, singletons, ambient context, mutable fields written from several methods. You cannot know the value from the code in front of you. 8. **Yo-yo reading / deep delegation** — a chain of one-line pass-through wrappers spread over files. Each hop costs a context switch and leaves an unresolved item. 9. **Inconsistent vocabulary** — `user`, `account`, `customer` for the same concept forces you to hold a translation table. 10. **Distance between related things** — the `if` and its `else` a screen apart; a variable's declaration far above its use. ## Techniques that reduce it - **Chunk by naming.** Extract a named function or a named intermediate boolean/value. The name becomes a single LTM-backed item and the details leave WM. This is the single highest-leverage move. - **Shorten live ranges.** Declare at first use, not at the top; extract the region using a variable into its own function so the variable dies with it. - **Prefer immutability.** `val`/`const`/final values remove the 'when did it change?' dimension. - **Discharge conditions early.** Guard clauses and `continue` retire a condition instead of carrying it. - **Kill flag parameters.** Split `render(doc, draft: true)` into `renderDraft(doc)` / `renderFinal(doc)`, or pass a named enum. - **Command/query separation.** A function that either returns a value or causes an effect — not both — removes hidden facts to remember. - **Locality / stepdown rule.** Uncle Bob's 'newspaper' arrangement: high-level function first, its helpers just below in call order, so reading proceeds downward without hunting. - **Consistent domain vocabulary** (ubiquitous language) so names map straight to LTM concepts. - **Make illegal states unrepresentable.** If a type guarantees non-null, already-validated data, that is a fact the reader never has to verify or hold. ## Trade-offs and edge cases - **Extraction is not free.** Each extracted function is a jump. Extraction pays off when the name fully summarises the body (the reader can skip it). It backfires when the helper needs six parameters, shares mutable state with the caller, or is named `handleStuff` — then you have added a hop without removing an item. This is the strongest argument against blindly minimising a per-function complexity score. - **Familiarity shifts the line.** A team fluent in a pattern chunks it for free; the same code lands hard on newcomers. Idiomatic-for-this-codebase beats clever. - **Measurement.** Cognitive complexity scores only control flow, so it misses most items on the list above — mutation distance, flags, hidden effects, vocabulary. Those need code review, not a linter.
- You extract a helper to lower a function's complexity score and reviewers say it got harder to read. What went wrong?The extraction did not create a real chunk. Either the name does not summarise the body (so the reader must open it anyway), or the helper needs many parameters / shares mutable state, so the caller's context travels with it. Extraction only helps when the name lets you skip the body.
- Why is a boolean flag parameter singled out as a readability problem?At the call site `send(msg, true)` carries no meaning, so the reader jumps to the declaration, decodes it, and carries the decoded fact back. It also means the function does two things. Splitting into two named functions or passing a named enum removes both problems.
- How does immutability reduce memory load specifically?An immutable value is one fact: name → value. A reassigned variable is a fact plus a history — the reader must track which assignment is live at each line, and any call in between might have changed it.
Reading code is like following a recipe while holding ingredients in your hands. Nesting is being told 'only if the oven is hot, and only if the dough proofed, and only if...' before each step. Naming things is putting an ingredient into a labelled bowl: one bowl to track instead of five items.
saying these in an interview costs you the question
- Equating readability with line count or with a low complexity score alone
- Assuming any extraction improves readability, regardless of the helper's name or parameter list
- Ignoring mutable non-local state because 'the function itself is short'
- Treating comments as a substitute for names that make the code self-describing
- Claiming readability is purely subjective, so nothing can be improved deliberately