Your team's standard bans linked lists outright. When would you overrule it, and how would you justify that?
answer
- concede the default before arguing
- name the property, not the structure
- worst-case latency, or stable identity
- can contiguous storage plus handles do it
- measure, scope, document, set an exit
basics
~20 sOverrule the ban only when a property, not a speed hunch, demands it: worst-case constant-time operations with no runtime allocation, or element identity that must survive unrelated mutation. Justify with a measurement, scope it to one module behind an interface, and document the exception.
solid answer
~50 sI would start by conceding the rule, because it is right almost always — contiguous storage wins on measured performance for traversal-heavy work, and a blanket default saves the team a recurring argument. Then I would argue from a *property* rather than from a complexity table. Two properties genuinely justify an exception: a worst-case constant-time bound with zero allocation on a deadline path, and stable element identity under unrelated inserts and removals. Before spending the exception I would check whether contiguous storage plus generation-tagged handles buys the same property, since it usually does and keeps locality. If the exception survives that, I make it cheap to live with: one module, a narrow interface stated in domain terms, a benchmark in the repository that shows the win, a test that asserts the property being bought, and a written note saying what would make us revert. The standard then gains a documented exception instead of being quietly violated.
go deeper
Know that contiguous storage is the sensible default for almost all collections, and that arguing for anything else needs a concrete reason plus a measurement rather than a remembered complexity table.
Be able to name the specific properties that justify an exception — a worst-case bound with no allocation, or references that stay valid under mutation — and explain why plain insertion cost is not one of them.
Show that you check the counterproposal first: contiguous storage with generation-tagged slot handles or a fixed-capacity free stack usually buys the same property while keeping locality. Bring a benchmark before bringing the structure.
Own the standard itself. Decide whether it should name its exceptions rather than be quietly violated, weigh the maintenance tax on every future reader against the measured win, and set the exit condition and the scope boundary before the code lands.
## Why the ban exists and why conceding it is the right opening Blanket rules in engineering standards are usually compressed experience. "Prefer contiguous storage" encodes a real and repeatedly measured fact: for the overwhelming majority of collections, striding through adjacent memory beats chasing pointers, per-element allocation is a cost that textbook complexity tables do not show, and the asymptotic advantage a linked structure appears to have on insertion evaporates once you account for having to *find* the insertion point. A team that argues this from scratch every sprint is wasting time, so the rule buys focus. An engineer who opens by attacking the rule has already lost the room. The productive opening concedes it, then narrows the discussion to the specific case, because an exception granted against a rule you respect is durable, while one won by rhetoric gets reverted by the next reviewer. ## Argue a property, never a structure The strongest reframing is to stop asking "array or list?" and ask "which property does this code require that the default does not provide?" Properties that actually justify the exception: 1. **A worst-case bound with no runtime allocation.** On a hard-deadline path, amortized guarantees are the wrong currency: a growth step that copies everything is exactly the operation that misses the window. A structure that never grows and never allocates gives a bound on every call, not on the aggregate. 2. **Stable element identity under unrelated mutation.** When long-lived references to individual elements must remain valid while the collection is inserted into and removed from elsewhere, position-based addressing is unsafe. Nodes do not move; slots get renamed. 3. **Membership in several collections at once.** When links are embedded in the element itself, one element can be threaded into multiple collections with no extra allocation and can be removed from all of them given only its address. 4. **Relinking a run of elements between collections without moving the elements.** Splicing rewrites a few links; the elements themselves are never copied. Note the caveat honestly: if a collection maintains a size counter, splicing a sub-range costs time proportional to the number of elements moved just to fix the counts. Notice that none of these is "insertion is O(1)". That claim is the weakest possible argument, because it silently assumes you already hold the position. ## Check the counterproposal first Before spending organisational capital, test whether contiguous storage can supply the same property. It often can. Stable identity is available from a slot allocator over an array that never compacts, with a generation counter per slot so stale handles are detected. A worst-case constant-time pool is available from a preallocated stack of free indices with a fixed capacity. Both keep locality and both stay inside the team's default idiom. If the counterproposal works, take it — you get the property and no exception. The exception is only justified when the counterproposal genuinely fails: no relocation is permissible because other subsystems hold raw references, elements must be embedded in objects owned elsewhere, or the side storage the array design needs is itself the thing you cannot afford. ## Making the exception cheap to live with This is the part that distinguishes a lead's answer from a strong engineer's. The cost of an exception is not the code; it is the ongoing tax on everyone who reads it later. So: - **Scope it.** One module, one file if possible, behind an interface expressed in domain terms — reserve, release, cancel — so callers do not learn that a linked structure exists. - **Prove it.** A benchmark checked into the repository, run in the same conditions as production, showing the property being bought. "It should be faster" is not an argument; a measurement is. - **Test the property, not the implementation.** Assert the worst-case bound or the handle validity directly, so a future rewrite must preserve the reason the exception was granted. - **Write the exit condition.** State what would make the exception unnecessary — a hardware change, a data-size change, an access-pattern change — and where the fallback lives. - **Amend the standard.** Change the rule from "never" to "never, except for these named properties, which require a benchmark." A standard that is quietly violated is worse than one that names its exceptions. ## What the exception looks like at ten times the volume Ask what breaks when the collection is ten times larger. Scans of a linked structure degrade worse than scans of contiguous storage because misses accumulate, so an exception that was invisible at today's size can become the profile's hot spot. If the design's hot path is traversal, growth will eventually kill it; if the hot path is unlink-by-handle and traversal is rare, growth changes nothing. Knowing which of these you have is the difference between an exception that ages well and one that becomes a rewrite. ## Not one structure, but a family It is worth noting that the real-world compromise is rarely a strict one-element-per-node chain. Mainstream runtimes made different calls on the same idea: both C++ and Python implement their double-ended queues over fixed-size chunks of contiguous memory rather than one node per element — Python links the chunks together, C++ indexes them — which keeps the constant-time ends while restoring most of the locality. When someone declares linked structures obsolete, the accurate correction is that per-element chaining lost the general case, while chunked and intrusive variants are alive throughout production systems.
- A colleague says modern hardware made linked structures obsolete. What is the accurate correction?That per-element chaining lost the general case, not every case. Two niches survive on properties rather than speed: worst-case constant-time operations with no runtime allocation, and element identity that survives unrelated mutation. Chunked and intrusive variants are also common in production, which is why the blanket claim overshoots.
- What would make you withdraw the exception after shipping it?A measurement showing the win is gone — the data set or access pattern changed and traversal now dominates — or a maintenance bill appearing as recurring bugs at the module's boundary. Because the structure sits behind a narrow interface, reverting is a local change, which is exactly why the interface was worth insisting on.
- How do you stop the exception from spreading through the codebase?Keep it behind an interface stated in domain terms so callers never see the structure, forbid handing raw node references across the module boundary, and add a test asserting the property that justified it. Anyone wanting the same pattern elsewhere then has to argue their own case with their own benchmark.
saying these in an interview costs you the question
- Argues from a textbook complexity table instead of a measurement
- Claims linked lists are always faster for insertion
- Accepts the ban silently and ships an unnecessary linear scan
- Introduces the structure with no benchmark and no fallback
- Leaks raw node references across module boundaries