← Skill tree CS Skill Tree 0 CSCD211

The No-Dummy-Head Linked List: Head, Size, and Why They Are Enough

Textbook: Liang

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

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:

  1. addLast(1): list is empty, so head becomes [1 | null]. size = 1.
  2. addFirst(0): new node [0 | *] points to [1 | null]; head becomes [0 | *]. size = 2.
  3. addLast(2): walk from head: cur starts at [0], moves to [1] (whose next is null); 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?

Tier 1 · Liang 10e Ch 24

What is the Big-O cost of addFirst on an NDH linked list with n elements?

Tier 1 · Liang 10e Ch 24

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);

Tier 2 · Liang 10e Ch 24

Why does the course track size as a stored field rather than computing it by counting nodes each time?

Tier 2 · Liang 10e Ch 24

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?

Tier 3 · Liang 10e Ch 24