skip to content

questions

4

Why does a dummy head node remove the special case for deleting the first element?

level: juniorimportance: must knowfreq 66%

answer

  1. Think about which node lacks a predecessor
  2. Each branch exists for one input shape
  3. What if position zero had a parent
  4. Relinking always goes through prev.next
  5. Handle points at sentinel, head never moves

basics

~20 s

A dummy head is a permanent node placed before the first real element, so every real node has a predecessor. Deletion is always relink-the-predecessor, which means the first element needs no branch of its own.

solid answer

~40 s

In a plain singly-linked list the first node is the only one with no predecessor, so every mutation that unlinks a node, or inserts before one, has to special-case position zero and also reassign the external head reference. A dummy head is a payload-free node that the list handle always points at: the first real element is `sentinel.next`, and an empty list is simply the state `sentinel.next == null`. Deletion then becomes one uniform `prev.next = prev.next.next` with `prev` starting at the sentinel, and insertion one uniform `prev.next = fresh`. The head reference never moves, so the whole class of "forgot to reassign head" defects disappears. The cost is one node per list, plus the discipline of handing callers `sentinel.next` and never the sentinel itself.

go deeper

for a junior

Be ready to say what the sentinel is, where the list handle points, and that the first real element is the node after it. Naming the branch it deletes is enough at this level.

for a middle

Explain why the first node is the only one without a predecessor, then show how insert and delete collapse into a single relink once the sentinel supplies that predecessor.

for a senior

Make the maintenance argument: the head branch gets copied into every mutating routine, is exercised by one input shape, and is where head-reference bugs live. Price the cost honestly at one node per list.

for a principal

Own the call about where the pattern is worth standardising: a widely-copied container with many mutating operations, versus millions of tiny lists where one extra node each is a real memory line item.

## The node that has no predecessor A singly-linked list is a chain of heap-allocated nodes, each holding a payload and a reference to the next node; the list object itself holds one reference — the head — to the first node. Every node in that chain is reachable from its predecessor, except one: the first. That single asymmetry is the source of nearly every edge case people write in list code. Unlinking a node means giving its predecessor a new `next`. Position zero has no predecessor, so the routine must do something structurally different: reassign the head reference stored in the list object. Inserting at position zero has the same shape. A hand-written delete therefore grows three paths — one for the empty list, one for "the target is the first node", and one general scan. ## What the sentinel changes A dummy head (sentinel) is a permanently allocated node placed before the first real element. It carries no payload the application cares about, it is never returned to callers, and it is never removed. The list holds a reference to the sentinel rather than to the first element; the first real element is `sentinel.next`; the empty list is exactly the state `sentinel.next == null`. Now position zero has a predecessor. Delete becomes: start `prev` at the sentinel, walk while `prev.next != null`, and when `prev.next` is the target, set `prev.next = prev.next.next`. That one line handles a first, middle or last element identically, and it handles the empty list with no test at all, because the loop body simply never runs. Insertion is the same story: `fresh.next = prev.next; prev.next = fresh`, with `prev = sentinel` for a prepend. ## Why this is a bug class, not a style preference Three arguments, in ascending order of how much an interviewer cares: 1. **Branch count.** Three code paths become one, so the path that used to be exercised only by two rare input shapes is now exercised by every input. 2. **Duplication.** The head branch is not written once — it reappears in delete, insert-at, splice, filter and every other mutating routine. Each copy is a fresh chance to forget one of the two updates it owes. 3. **The head reference.** The head branch is the only one that writes state outside the chain. Forgetting that write, or ordering it wrongly, leaves the list pointing at a node that has already been unlinked — the classic "the deleted entry is still there" or "the list suddenly looks empty" defect. Consider an append-only ledger of signed amounts with timestamps, kept newest-first. Reversing the most recent entry always removes the first node, so the head branch is not a rare corner — it is the hot path, while the general loop that collects all the incidental testing almost never runs on real traffic. A sentinel deletes that asymmetry outright. ## What it costs One node's worth of memory per list — negligible for a few long lists, real if you keep millions of two-element lists. One extra dereference to reach the first element. And a discipline: everything that walks, counts or reports on the list must begin at `sentinel.next`, and the sentinel must never escape the structure. A length routine that starts at the sentinel returns n+1; a search that compares the sentinel's default payload can return a phantom match. ## Design variants - **Head-only sentinel, null-terminated.** Removes the empty-list and head branches; reaching the tail still costs a scan. - **Head and tail sentinels.** Every real node has both a predecessor and a successor, so insert-before, insert-after and remove are uniform at both ends, at the cost of two permanent nodes per list. - **Sentinel ring** (doubly linked, both ends wrapping through one sentinel). No null appears anywhere in the structure; iteration terminates by arriving back at the sentinel instead of by hitting null. ## Trace the single-element case once With a sentinel and exactly one real node X: `sentinel.next == X` and `X.next == null`. Deleting X runs the general loop, matches with `prev == sentinel`, sets `sentinel.next = X.next` which is null, and the list is empty — a state the code already understands. Without a sentinel, that same delete must notice both that the head is going away and that the list becomes empty; two conditions inside one branch is precisely where single-element bugs are born.

  • How does a caller get the real first element, and what goes wrong if the sentinel leaks out?
    The structure exposes `sentinel.next`, never the sentinel itself. If the sentinel leaks into iteration, length or search, callers see a phantom entry with a meaningless payload, and a search for a default-valued key can even match it. Keep the sentinel private and start every traversal at `sentinel.next`.
  • Does a dummy head also help a singly-linked list that needs fast appends?
    Only partly. It removes the empty and head branches from insertion, but reaching the last node is still O(n) unless you keep a tail reference too. A tail sentinel that always sits after the last real node gives append the same uniform shape, at the cost of a second permanent node and keeping both ends consistent.
  • Does a sentinel buy anything for read-only traversal?
    Almost nothing. Iteration and search must skip it, so the code gets marginally more careful rather than simpler. The payoff is entirely on the mutating side, where the missing predecessor at position zero is what forces the extra branches.

saying these in an interview costs you the question

  • Says the sentinel stores a real element of the list
  • Claims you still need a branch for the empty list
  • Thinks the sentinel is reallocated on every insert
  • Calls it pure style with no bug-class argument
  • Returns the sentinel itself to callers as the head

context

open as a page

In a doubly-linked sentinel ring, why do insert and remove need no null checks?

level: middleimportance: should knowfreq 46%

basics

~20 s

In a sentinel ring every node always has a non-null prev and next, because both ends wrap through the sentinel. Insert and remove become a fixed handful of pointer assignments with no null or end-of-list tests.

open as a page

Why is a three-branch delete (empty, head, scan) a dummy-head refactor and not just style?

level: seniorimportance: should knowfreq 40%

basics

~10 s

All three branches exist because the first node has no predecessor. A dummy head supplies one, collapsing them into a single scan-and-relink and deleting the head-reference reassignment where the real defects live.

open as a page

Which bugs does a dummy head sentinel fail to prevent, and which one does it add?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

A sentinel only removes the missing-predecessor branches. Length, search, iteration and the value handed back to callers must all start after it, and a sentinel carrying a default payload can be matched by a scan that forgets to skip it.

open as a page