In an order book where cancels hold a node handle, what does a linked list guarantee that a compacted array does not?
answer
- position versus identity
- what does compaction do to stored indices
- the handle must survive unrelated edits
- nodes never move, slots get renamed
- silent wrong-order cancel, not a crash
basics
~20 sA linked list gives every resting order a stable identity: unrelated inserts and removals never move a node, so a handle taken at insert time is still valid at cancel time. An array index names a position, and compaction or growth silently renames every later order.
solid answer
~50 sThe property being bought is **reference stability under mutation**, not speed. When an order rests, the book hands back a handle; when the cancel arrives, that handle must still identify the same order after thousands of unrelated inserts and executions. Nodes never relocate, so the handle stays valid and the cancel is a constant-time unlink with no search. An array index does not survive: compacting after a removal shifts every later element down, and growing the array relocates the whole block, so a stale index quietly addresses a *different* order — a silent wrong-order cancel, which is far worse than a crash. You can get stability over an array, but only by never compacting: a slot allocator with its own free list and a per-slot generation counter, so stale handles are detected rather than misinterpreted. The list's price is pointer chasing when scanning a price level.
go deeper
Know that a linked node keeps the same identity for its whole life while an array index describes a position that other operations can change, and that this is what makes a saved reference safe or unsafe later.
Explain concretely what compaction and growth do to stored indices, and why an unrelated insert or removal leaves a node handle untouched. Be able to state the cancel path's cost with a handle in hand.
Diagnose the failure mode as silent rather than fatal, price the locality you give up, and propose the generation-tagged slot design as the alternative that keeps contiguous storage while turning stale handles into detectable errors.
Decide which invariant the system is actually buying and who must maintain it. Weigh a structure your team can reason about against one that is faster but leaks raw references across module boundaries, and set the rule for how identity is exposed.
## Position versus identity Every container answers "which element do you mean?" in one of two ways. An array answers by **position** — the third slot — and position is a property of the current layout. A linked structure answers by **identity** — this node — and identity is a property of the element itself. The distinction is invisible until the container mutates, and then it decides whether a stale reference is safe, wrong, or detectably wrong. ## The workload An order book holds resting orders, grouped by price level and kept in arrival order within a level so that matching is fair. Three things happen constantly: new orders rest, orders at the front execute and leave, and orders anywhere in the middle are cancelled by their owner. The cancel path is the interesting one. It arrives with a client-supplied identifier, and the book must find that specific order and remove it, ideally without scanning the level — cancels are frequent and latency-sensitive. So the book keeps, somewhere, a mapping from client identifier to "where the order lives". The question is what "where" can safely be. ## Why an index is not an answer Suppose the level is a contiguous array and "where" is an index. - **Removal plus compaction.** When an order in the middle leaves, the elements after it are slid down to keep the level contiguous. Every stored index above the removed slot is now off by one and refers to a neighbouring order. Nothing errors; the next cancel simply removes the wrong order. - **Growth.** When the level exceeds capacity, the whole block is relocated. Indices still point at the right logical positions, but any raw pointer taken into the block is dangling. - **Tombstones instead.** You can refuse to compact and leave holes, which keeps indices stable — but now the level accumulates dead slots, scans get slower, and you need a reclamation strategy. This is the beginning of a real design, not a refutation. The failure mode that matters is the first one: it is *silent*. A dangling pointer usually crashes; a shifted index produces a plausible, wrong action. In a book, that is a cancelled order that the client never cancelled. ## What the list guarantees In a doubly linked level, a node's address is fixed for its whole life. Inserting elsewhere in the level rewrites two links between existing nodes; it does not touch the handle you hold or the node it points at. Removing some other order likewise touches only that order's neighbours. So: - **The handle survives arbitrary unrelated mutation.** No versioning, no fix-ups, no re-lookup. - **Cancel is constant time end to end.** The handle is the location, so there is no search; the unlink is a fixed number of pointer writes, and the order's neighbours close around it. - **Order within the level is preserved exactly.** Removing from the middle changes nobody else's relative position, which is the fairness property the book has to maintain. ## What it costs, and the mature alternative The list buys stability with locality. Walking a price level chases pointers instead of striding through contiguous memory, so scans prefetch poorly. The standard mitigation is to allocate all nodes from one contiguous arena rather than individually, which restores much of the locality and, as a bonus, makes node allocation a bump or a free-list pop instead of a general-purpose request. The genuinely mature answer is that the property is separable from the structure. You can build stable handles over an array: never compact, allocate slots from a free list of vacated slots, and store a **generation counter** in each slot that increments on reuse. A handle is then (slot, generation); on cancel you compare generations, and a stale handle is *detected* rather than silently misapplied. You keep contiguous storage and locality, you pay one comparison and some permanently reserved slots, and you have converted a silent wrong-order bug into an explicit "already gone" response. That last conversion — from silent corruption to a checkable error — is usually the strongest argument in the room. When does the list still win? When the elements must be members of more than one collection at once (an order threaded into both its price level and its owner's list, with links embedded in the order itself), when relocation is not permitted at all because other subsystems hold raw references, or when the constant-time splice of a whole run between collections matters. ## How to answer it Name the property first — stable identity under unrelated mutation — then show the failure mode of the index version, then price both sides and offer the generation-tagged slot design as the alternative. A candidate who only says "linked lists have O(1) deletion" has answered a different, easier question.
- Can you get stable handles over contiguous storage instead?Yes, by never compacting. Allocate slots from a free list of vacated slots and store a generation counter per slot that increments on reuse; a handle is (slot, generation) and a stale one is detected on comparison rather than silently applied. You keep locality and pay a check plus slots reserved while any handle might exist.
- What does the linked level cost the matching path?Locality. Walking a price level chases pointers rather than striding contiguous memory, so prefetching helps far less and each step risks a cache miss. Allocating all nodes from one arena recovers much of it and also removes per-node allocation from the hot path.
- Why is a shifted index worse than a dangling pointer?Because it is plausible. A dangling pointer usually faults and the failure is loud and immediate; a shifted index still addresses a live, valid element, so the system cancels a real order that nobody asked to cancel and the bug surfaces later as a reconciliation mismatch rather than as a stack trace.
A node handle is a house key: the house does not move when a neighbour's is demolished. An array index is a seat number in a row — renumber the row and every ticket now points at someone else's seat.
saying these in an interview costs you the question
- Says just store the index and move on
- Believes removal leaves other stored indices untouched
- Forgets that growth relocates the whole contiguous block
- Treats the failure as a crash rather than a silent wrong action
- Claims stability makes the locality cost irrelevant