The No-Dummy-Head Linked List: Head, Size, and Why They Are Enough
Try This First
A singly linked list stores head (a reference to the first node) and size (the count of nodes). The list is currently empty.
You call addFirst(42). After the call, what does head reference?
Reveal
head references a new Node<Integer> whose data field holds 42 and whose next field is null. Before the call head was null; addFirst creates one node and sets head to it.
What You Need To Walk In With
- A no-dummy-head (NDH) list keeps exactly two fields:
private Node<T> head;andprivate int size;. - When the list is empty,
headisnullandsizeis0. addFirstis O(1): it only touches theheadreference, regardless of list length.addLastis O(n): it must walk every node fromheaduntil it finds the one whosenextisnull.- Storing
sizeas a field keepssize()O(1). Computing it on demand by walking the list would cost O(n) every call.
How It Works
The two-field skeleton looks like this:
public class LinkedList<T> {
private Node<T> head; // null when the list is empty
private int size; // always kept in sync with actual node count
public LinkedList() {
head = null;
size = 0;
}
}
addFirst places a new node before the current head. Because you only update one reference, the cost is always O(1):
public void addFirst(T data) {
Node<T> newNode = new Node<>(data);
newNode.next = head; // new node points to old head (null if empty)
head = newNode; // head now points to the new node
size++;
}
addLast must find the tail before it can link a new node. That walk is the source of the O(n) cost:
public void addLast(T data) {
Node<T> newNode = new Node<>(data);
if (head == null) { // special case: empty list
head = newNode;
} else {
Node<T> cur = head;
while (cur.next != null) {
cur = cur.next; // walk until the last node
}
cur.next = newNode; // link new node after the tail
}
size++;
}
A pointer sketch after addLast(30) on a list that already holds [10 -> 20]:
Before:
head --> [10 | *] --> [20 | null]
After addLast(30):
head --> [10 | *] --> [20 | *] --> [30 | null]
^
cur stops here; cur.next = newNode
Notice that addLast checks head == null before attempting cur.next. Without that guard, an empty list would produce a NullPointerException on the very first call.
Worked Example: Predict, Then Check
A list is built with these three calls in order:
list.addLast(1);
list.addFirst(0);
list.addLast(2);
Predict the order of elements from head to tail, and predict the value of size.
Reveal
Step-by-step:
addLast(1): list is empty, soheadbecomes[1 | null].size = 1.addFirst(0): new node[0 | *]points to[1 | null];headbecomes[0 | *].size = 2.addLast(2): walk from head:curstarts at[0], moves to[1](whosenextisnull);cur.next = [2 | null].size = 3.
Final state: head --> [0] --> [1] --> [2 | null]. size = 3.
A Common Mistake
Forgetting the empty-list guard in addLast is the most frequent error. A student writes:
Node<T> cur = head;
while (cur.next != null) { // BUG: cur is null when list is empty
cur = cur.next;
}
cur.next = newNode;
When the list is empty, head is null, so cur starts as null. The expression cur.next immediately throws a NullPointerException. The fix is to check if (head == null) first and set head = newNode directly in that branch, then proceed with the walk only in the else branch. This pattern appears consistently across all quarter lab archives for the NDH list. (Source: Liang 10e Ch 24)
Go Deeper (optional)
Once you are comfortable with the basic NDH structure, consider the cost tradeoff more carefully. Every addLast call pays O(n) because you have no direct reference to the tail. One natural upgrade is to add a tail field alongside head, which reduces addLast to O(1) as well. That variant is covered in a later concept. Another future topic, the dummy-head design, eliminates the empty-list special case by keeping a permanent sentinel node before the first real element. Both improvements build directly on the NDH foundation you have here.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
Which two fields does a no-dummy-head singly linked list declare at the class level?
What is the Big-O cost of addFirst on an NDH linked list with n elements?
After these calls on an empty NDH list, what does the third statement print? list.addFirst(“B”); list.addFirst(“A”); System.out.println(list.size);
Why does the course track size as a stored field rather than computing it by counting nodes each time?
A student writes addLast without an empty-list guard: Node<T> cur = head; while (cur.next != null) cur = cur.next; cur.next = newNode; What happens when this method is called on an empty list?