Is the prev pointer worth its memory across 10 million nodes, and what do you lose without it?
answer
- Compute the field's cost out loud first
- One reference per node, times ten million
- Where do node references come from?
- A traversal already knows the predecessor
- Head-node branch is the hidden cost
basics
~20 sKeep prev only for backward movement or O(1) unlink at externally held nodes. At ten million nodes it costs a machine word each — tens of megabytes. If deletes happen during a traversal you already run, a trailing reference does the same for free.
solid answer
~60 sPrice it, then match it to the access pattern. A `prev` field is one reference per node — roughly 80 MB across 10^7 nodes on a 64-bit machine, plus the pressure that extra footprint puts on cache and allocation. What it buys is specific: backward iteration, O(1) insert-before, O(1) unlink of a node handed to you from outside, and O(1) delete-last given a tail reference. If your deletes all happen inside a forward pass you are already making — a filter or compaction sweep — a singly-linked list with a trailing reference to the previously visited node unlinks in O(1) with zero per-node cost, and that covers a large share of real workloads. Where it does not is where an external index hands you an arbitrary node, or where users step backwards. The second half of the call is maintenance: the trailing-reference idiom has a head-node special case every future maintainer must get right, so only trade the memory for that class of bug when the memory is actually the binding constraint.
go deeper
Know that a doubly-linked node costs one extra reference and that the extra field is what enables stepping backwards. That per-node cost times a large node count is a real number worth being able to compute.
Explain the exact operation set the prev field buys — backward iteration, insert-before, unlink at a held node, delete-last with a tail reference — and that none of it speeds up finding a node in the first place.
Demonstrate the sweep alternative: a trailing reference during a forward pass gives constant-time removal with no per-node cost, and its head-node branch is where the defects appear. Know exactly which workloads it fails to cover.
Own the call under a real ceiling: price the field against the fleet's headroom, weigh it against the maintenance cost of the trickier idiom, and account for reversibility once callers hold node handles and depend on constant-time removal.
## Price the field before arguing about it A `prev` reference is one pointer-sized field per node. On a 64-bit machine that is 8 bytes; across 10^7 nodes it is about 80 MB of pure bookkeeping, before the payload. Whether 80 MB is a rounding error or the whole argument depends on the ceiling: on a fleet of instances sized to a fixed memory limit per process, it can be the difference between fitting the working set and not, and it is multiplied by every replica. The right first move in the interview is to compute it out loud rather than to reason about it qualitatively. Three caveats keep the estimate honest. First, the *relative* cost depends on the node's payload — for a node holding a value and a `next`, adding `prev` grows it by half; for a node holding a fat record, the extra field is noise. Second, per-node allocation overhead may already dwarf the field, in which case the marginal decision is smaller than it looks. Third, if the structure is dense and long-lived, nodes can be held in a preallocated block and linked by integer indices rather than references, which shrinks both link fields to 4 bytes and makes the `prev` question cheaper on both sides. ## What the field actually buys Be precise, because the usual answer over-claims: - **O(1) unlink of a held node.** Removing a node means writing its predecessor's `next`; `prev` names that predecessor in one hop. - **O(1) insert before a held node** — same reason. - **O(1) delete-last given a tail reference**, via `tail.prev`. - **Backward iteration at O(1) per step.** And what it does not buy: nothing about *finding* a node. Deletion by value stays O(n) in both variants, because the scan dominates. If your workload's deletes are value-driven, `prev` changes the constant factor and nothing else — that alone can settle the question. ## The case where a trailing reference wins outright A large fraction of real deletions happen during a sweep the code already performs: walk the list, drop the entries that match a predicate, keep the rest. In that shape you do not need `prev` stored in every node, because the traversal *is* the predecessor: carry a second reference one step behind the cursor, and unlinking the current node is one write. Zero extra bytes per node, constant-time removal, and the pattern generalises to any single-pass filter, compaction or expiry scan. The catch is a real one. The trailing reference is NIL while the cursor is on the first node, so removing the head is a separate branch that updates the list's handle instead of a node's field. That branch is where the bugs live, and it is worth naming in an interview: you are trading 80 MB against a special case that every future maintainer of that loop has to re-derive. ## The cases where a trailing reference cannot help Draw the line clearly: 1. **Node references arriving from outside the list.** If a separate index maps keys to nodes so callers can remove arbitrary elements in constant time, there is no traversal in flight and no trailing reference to have. Without `prev`, every such removal degenerates to an O(n) walk, and the index's whole purpose evaporates. 2. **Backward movement as a user-visible operation.** A media player's previous-track control steps backwards from wherever the cursor sits. Without `prev` each step is a re-walk from the head, so a listener tapping back repeatedly pays a full traversal each time — asymptotically catastrophic on a long queue and easy to observe as input lag. 3. **Insert-before at a held position**, for the same reason as unlink. If any of these is on the critical path, the field is not overhead; it is the feature. ## Making the call The decision procedure worth stating: enumerate the operations on the hot path and their frequencies; check whether every removal can be co-located with a traversal you already run; price the field against the actual ceiling rather than against intuition; and only then weigh the maintenance cost of the trickier idiom. If the memory is comfortable, keep `prev` — the code is simpler, the failure modes are fewer, and the operation set is strictly larger. If the ceiling is genuinely binding and the access pattern is sweep-shaped, drop it, and put the head-node branch behind one reviewed helper rather than open-coding it at every call site. Two further points a lead should own. **Reversibility:** dropping `prev` is easy to undo in code but hard to undo in an API — once callers hold node handles and expect constant-time removal, adding the field back is a data migration on a live structure, not a patch. Design for the operation set you will plausibly need, not the one you have this quarter. **Scale honesty:** if 10^7 nodes are actually being scanned, the variant choice may be the second-order question. The memory ceiling that makes `prev` unaffordable is often the same ceiling that argues for an index or a different representation entirely, and a lead is expected to say so rather than optimise a field within a structure that no longer fits the workload.
- How would you decide, concretely, without guessing?List the hot-path operations and their rates, then ask one question of each removal: is a traversal already in flight when it happens? If yes, a trailing reference covers it. Price the field against the real ceiling — a word per node times the node count — and compare that number to the headroom you actually have, not to intuition.
- What is the hidden cost of the trailing-reference idiom?The trailing reference is NIL while the cursor sits on the first node, so removing the head is a separate branch that rewrites the list handle rather than a node field. That branch is the bug magnet, and it recurs at every sweep site. Put it behind one reviewed helper instead of open-coding it repeatedly.
- You dropped prev and a year later the product wants backward navigation. How bad is that?In code, cheap; in a live system, not. Callers may already hold node handles and expect constant-time removal, so restoring the field means rebuilding or migrating the structure while writes continue. That reversibility cost is why the decision should reflect the operation set you plausibly need, not only this quarter's.
- Does dropping prev change deletion by value?Not asymptotically. Deletion by value is O(n) in both variants because the scan dominates, and the unlink is noise inside it — though without `prev` you must remember to carry the trailing reference during that scan rather than re-walking. If your deletes are all value-driven, the field is buying you very little.
saying these in an interview costs you the question
- Argues the memory cost qualitatively without computing a number
- Says the extra field is always negligible at any scale
- Claims a trailing reference replaces prev for externally held nodes
- Ignores backward iteration when listing what prev buys
- Treats dropping prev as freely reversible on a live structure
- Forgets the head-node branch in the trailing-reference sweep