androidengineers.Book a session

Linked Lists

Circular Lists and Sentinel Nodes

article22 minMedium

Handling boundary conditions (head == null, inserting at index 0, removing the last element) often leads to messy code cluttered with if-else edge checks. Two elegant techniques solve these issues: Circular Lists and Sentinel (Dummy) Nodes.


Circular Linked Lists

In a standard linked list, the tail node's next pointer points to null. In a Circular Linked List, the tail's next pointer loops directly back to the head node!

+---------------------------------------+
|                                       |
v                                       |
[ 10 | * ] ---> [ 20 | * ] ---> [ 30 | * ]
Head                             Tail

Why Circular Lists?

  1. Continuous Round-Robin Cycling: Any node can serve as a starting point for full list traversal.
  2. O(1) Access to Head and Tail: If you maintain a single pointer to tail:
    • tail gives you the last element in O(1).
    • tail.next gives you the head in O(1)! You don't even need a separate head pointer!

Real-World Use Cases:

  • Operating System Task Schedulers: Round-robin CPU time-slice distribution across threads.
  • Media Players: Repeat playlist feature that cycles continuously without re-initialization.
  • Multiplayer Turn-Based Games: Cycling through player turns (Player 1 -> Player 2 -> Player 3 -> Player 1).

Sentinel (Dummy) Nodes

A Sentinel Node is a dummy node that holds no meaningful data and exists solely to anchor the head (and optionally tail) of a linked list.

Without Sentinel:
Empty list: head = null
Add first item: Must write special if (head == null) { head = newNode }

With Sentinel:
Empty list: dummyHead ---> null
List with 1 item: dummyHead ---> [ Data: 10 | null ]

Eliminating Edge Cases in Code

Observe how sentinel nodes eliminate special-case if (prev == null) checks:

// Removing a node with value 'x' WITHOUT sentinel
fun removeValue(head: Node<Int>?, target: Int): Node<Int>? {
    var h = head
    // Special case 1: target is at head
    while (h != null && h.data == target) {
        h = h.next
    }
    var curr = h
    // Standard case: target in middle or tail
    while (curr?.next != null) {
        if (curr.next!!.data == target) {
            curr.next = curr.next!!.next
        } else {
            curr = curr.next
        }
    }
    return h
}

// Removing a node with value 'x' WITH Sentinel Node
fun removeValueClean(head: Node<Int>?, target: Int): Node<Int>? {
    val sentinel = Node(0, head) // Anchor dummy before head
    var curr = sentinel
    
    while (curr.next != null) {
        if (curr.next!!.data == target) {
            curr.next = curr.next!!.next // Universal deletion!
        } else {
            curr = curr.next!!
        }
    }
    return sentinel.next // Clean new head
}

Doubly Linked List with Head and Tail Sentinels

The gold standard implementation of a Doubly Linked List uses two sentinels: head and tail.

[ DUMMY HEAD ] <=======> [ Real Node A ] <=======> [ Real Node B ] <=======> [ DUMMY TAIL ]

Every real node is guaranteed to have both a valid prev and a valid next. Pointers never dereference null, eliminating NullPointerException errors entirely!


Summary

  • Circular lists connect tail to head, ideal for round-robin scheduling and infinite playlists.
  • Sentinel nodes eliminate edge-case null checks during insertions and deletions, producing cleaner, bug-free linked list operations.

YOUR LEARNING JOURNEY

0 of 55 available lessons completed

Progress saved in this browser. No account needed.
Circular Lists and Sentinel Nodes | Data Structures | Android Engineers