Concepts / Traversing a Linked List

Traversing a Linked List

Deleting the first node means changing head to head.next, so the old first node is no longer reachable from the list.

  • Programming
Interactive lab

Try it: Linked List

How a singly linked list stores values in separate nodes joined by next pointers, and how inserting or deleting only rewires pointers.

How it works

  1. HEAD points at the first node; each node holds a value and a pointer to the next node; the last node points to NULL.
  2. To insert, create a node, store its value, point it at its successor, then point its predecessor (or HEAD) at it.
  3. To delete, walk to the node and make its predecessor skip over it.
  4. Searching means following next pointers from HEAD until the value or NULL is reached.

Default run (9 steps): HEAD → 10 → 20 → 30 → NULL. 3 of 10 nodes. Choose an operation. Example: append 40. … Done: HEAD → 10 → 20 → 30 → 40 → NULL.

Educational simulation

Loading the simulation…

Reading a Delete Method First

This article starts from code rather than from a definition. A linked list is a chain of nodes, where each node holds an item and a next reference to the following node. The list itself is reached through a variable called head. Deleting an item from a linked list is a close cousin of inserting one, but it is simpler, because you only rewire existing references rather than create new nodes. The hard part is making sure the rewiring happens in the right order and in the right places, and that is where most bugs in this operation come from.

Keep the delete method in mind as you read. Its job is to remove only the first node whose item matches the target. It returns true when it finds and deletes the item, and false when no match exists. Read it line by line and keep track of which node each variable refers to. Predicting what a line does before reading the explanation is the fastest way to find the lines that matter.

Deleting the First Node

The first node is special because the list is reached through head. Nothing comes before the first node, so there is no previous node whose next field could be rewired. The only thing that can change is the reference held by head. The source describes this case as setting head = head.next. Because head.next refers to the second node, the new head is the old second node. Everything else in the list stays the same, and the old first node is no longer reachable from the list.

headheadAAno longer reachableBBCCheadnextnextheadnext
After head is set to head.next, which nodes can still be reached from head, and what happens to the old first node?
Worked example

Removing the first item from a three-node list

A list holds A, B, and C in that order, and head refers to the node holding A. Delete A using head = head.next.

  1. 1
    Start state

    head refers to the node holding A. That node's next refers to the node holding B, and B's next refers to C.

  2. 2
    Evaluate the right-hand side

    head.next is the reference stored in the first node's next field, which refers to the node holding B.

  3. 3
    Perform the assignment

    head now refers to the node holding B. No next field was changed, and no other node was touched.

  4. 4
    Check what is reachable

    Starting from head you reach B and then C. The node holding A has no live reference leading to it.

Result

The list now holds B and C. The node holding A is no longer reachable and can be reclaimed.

Removing a Node From the Middle

When the node to be deleted sits inside the list, head does not need to move. Instead, you need a reference to the node just before the target. The source uses two references for this. runner points to the node being deleted, and previous points to the node that precedes it. Once both are in place, the statement previous.next = runner.next removes the node. The next field of the previous node now skips over the deleted node and goes straight to the one after it.

ApreviousBrunner, to deleteCafter the targetnextnextprevious.next = runner.next
When previous and runner sit on either side of the target, which next pointer is rewired to bypass the target node?

After the rewiring, the node holding B has no live reference leading to it. The source notes that such a node will be garbage collected, so the program is free to reclaim its memory. You do not need to clear the deleted node's own next field to make this happen. The important point is that no live reference should still lead to it. The source also encourages you to draw this operation yourself, because the arrows are easy to lose track of when you read only text.

The delete method does not know in advance where the item is, so it searches first. The loop moves runner forward one node at a time, and previous follows one step behind. The loop stops either when runner finds the item or when runner runs off the end of the list. Keeping previous one step behind runner lets the method reach the node before the target without ever going backward. Only after the search finishes does the method decide which of the two cases applies. The following sketch follows the same logic as the textbook's delete method for a linked list.

java
Notes on the code
  • previous starts as null because nothing comes before the first node
  • runner starts at head and is the only variable that moves during the search
  • the loop stops at the first match or when runner becomes null
  • previous is set to runner before runner advances, so previous stays one node behind
  • if runner is null, no match exists and the method returns false
  • previous is null only when the target is the first node, so head is updated
  • otherwise the node before the target skips over it

The javadoc for the textbook's method says that if several copies of the item are present, only the first one in the list is deleted. This follows directly from the loop. Because the search stops at the first match, later copies are never reached.

Where the Delete Method Diverges

Most delete bugs come from a single line that works for a middle node but fails for another case. In the sketch above, the line previous.next = runner.next is the one that diverges from expectation when the target is the first node. In that case previous is still null, because nothing was ever placed behind the first node. Following a null reference to change its next field cannot work. The branch that tests previous == null exists to send the first-node case to head = head.next instead.

  • Common mistake

    Treating the first node like a middle node

    What it looks like

    Writing previous.next = runner.next without checking whether previous is null, then deleting the first item of a list.

    Why it fails

    The first node has no previous node, so there is no next field to rewire.

    Do this instead

    Handle the first-node case separately by setting head = head.next.

  • Common mistake

    Letting previous fall behind or move ahead of runner

    What it looks like

    Advancing runner before recording it in previous, so previous ends up two nodes behind the target.

    Why it fails

    The rewiring statement needs previous to sit directly before runner.

    Do this instead

    Record previous = runner first, then advance runner to runner.next.

  • Common mistake

    Expecting every duplicate to be removed

    What it looks like

    Calling delete on a list holding X, Y, X and expecting both X nodes to disappear.

    Why it fails

    The search stops at the first match, so later copies are never reached.

    Do this instead

    Call delete again if you need to remove another copy.

  • Common mistake

    Forgetting to handle a target that is not in the list, so the method does not return false

Why Head Must Stay Put

Suppose you need to print every item in a linked list. A natural first attempt is to use head itself as the moving pointer. You keep stepping head forward to the next node, print the item along the way, and stop when head becomes null. The loop looks clean and seems to do exactly what you want. The problem is what it does to the list.

What do you think happens?

If head was used as the walking pointer and the loop ran to the end, what can you still reach from the head variable?

Reveal answer
Answer:

Nothing. head is null after the loop, so the front of the list can no longer be reached through head.

head is the program's only doorway into the list. Moving it during the walk means the way back to the first node is gone.

Ahead before the loopBhead after one stepChead after two stepsnullhead after the loophead = head.nexthead = head.nexthead = head.next
If the loop moves head forward on each iteration, what does head point to after each step, and why does the list disappear?

Advancing head during a walk leaves the program with no way to reach the list afterward. The standard approach avoids this with three moving parts. First, a runner variable receives a copy of head, so both variables refer to the first node at the start. Second, the loop repeatedly processes the item in the node that runner currently refers to. Third, at the end of each pass, runner is advanced by following its next pointer. The loop stops when runner becomes null, which marks the end of the list. head is never changed during the walk, so it still points to the first node when the walk is done.

A Loop That Visits Every Node

Before
while (head != null) {
    System.out.println(head.item);
    head = head.next;
}

The walking variable is head, so the list is lost when the loop ends.

After
Node runner = head;
while (runner != null) {
    System.out.println(runner.item);
    runner = runner.next;
}

A copy named runner does the walking, so head still refers to the first node.

The same pattern becomes concrete when the processing step is printing. The following code prints every string stored in a list of Strings. As you read it, ask which variable is allowed to change and which variable must not.

Code walkthrough
Node runner = head;
while (runner != null) {
    System.out.println(runner.item);
    runner = runner.next;
}
  1. 1
    Line 1 runner is copied from head. Both variables now refer to the first node, and head will not change again.
  2. 2
    Line 2 the loop checks whether runner is null. For a three-node list it is not, so the body runs.
  3. 3
    Line 3 the item in the current node, A, is printed.
  4. 4
    Line 4 runner moves to the next node, which holds B. head is untouched.
  5. 5
    The cycle repeats for B and C. When runner moves past C, it becomes null, the check fails, and the loop ends.
  • runner receives a copy of head, so it starts at the first node
  • the loop condition tests runner for null, which marks the end of the list
  • the body processes the item in the node runner currently refers to
Output
A
B
C

The output assumes a generated list whose three nodes hold the strings A, B, and C in that order. The loop visits nodes in list order, so the strings print in the same order they appear in the list. An execution trace would show runner moving node by node while head keeps its original value throughout.

Checking Your Traces

EASY

A list holds X, Y, and Z, with head referring to X. Trace what happens when delete is called with Y. Name the variables that move, write the value of previous and runner at the moment the loop stops, and state which statement removes the node.

Hints
  • Start with previous as null and runner at X, then move both references one step at a time.
  • At the moment the loop stops, runner refers to Y and previous refers to X.
  • Since previous is not null, the else branch runs and rewires X.next to Z.
MEDIUM

Using the same traversal pattern as the printing loop, write a loop that counts how many items are in a list of Strings. Then state what your loop returns when head is null, and explain why it needs no special case.

Hints
  • Copy head into a runner variable and keep the loop condition runner != null.
  • Increment a counter inside the loop body and advance runner at the end of each pass.
  • If head is null, runner is null on the first check, so the body never runs and the count stays at zero.

Key Takeaways

  • Deleting the first node means setting head = head.next. The old first node then has no live reference and is no longer reachable from the list.
  • Deleting a middle node uses previous and runner: the statement previous.next = runner.next skips over the target, and previous must always sit one node behind runner.
  • The delete method searches first, removes only the first matching item, and returns false when no match exists. The first-node case and the only-node case must be handled separately.
  • Traversal should walk with a runner copied from head, because advancing head itself leaves the list unreachable. An empty list, with a null head, is handled safely by the same loop without a special branch.

Related concepts

More concepts starting with T