Circular Linked List
Take a regular linked list and change one thing: instead of the last node pointing to null, have it point right back to the first node. That's a circular linked list, a loop with no real end.
It can be built either as a singly-linked loop (one pointer per node) or a doubly-linked loop (two pointers per node). Because the chain never terminates, it's a natural fit for anything that needs to cycle repeatedly, like round-robin scheduling or a circular buffer.
Since there's no fixed "first" or "last" node anymore, you can start traversing from anywhere in the loop and eventually visit every node, handy for problems that are inherently cyclic rather than linear.
Key Property: The last node's next pointer always points back to the first node, creating a continuous loop.
Basic Operations
| Operation | Complexity | Description |
|---|---|---|
| Insertion at Head | O(1) | Add new node at beginning, point last node to new head |
| Insertion at Tail | O(1) | Add new node at end, point it to head (with tail pointer) |
| Deletion at Head | O(1) | Remove first node, update last node's pointer |
| Deletion by Value | O(n) | Traverse list to find and remove specific node |
| Traversal | O(n) | Loop through nodes until returning to starting point |
| Search | O(n) | Traverse list to find element |
How Does It Work?
Each node below is drawn as its two parts: the data and a next cell. The grey address above each box is where that node lives in memory, and the next cell holds nothing but an address — the location of the node that follows.
Here is the only structural difference from an ordinary singly linked list. In a linear list the last node's next would read null; here it reads 0x1A, the address of the head. That single value is what closes the loop, and it is why traversal has no natural stopping point:
Because that link always exists, the same list is often drawn as a ring instead — the same three nodes, just laid out so the wrap-around stops looking like a special case:
The practical consequence is that a traversal cannot stop on "next is null", because that never happens. Instead you remember the node you started on and stop when you come back around to it — miss that and you have an infinite loop.
Insertion Process
Inserting at the head takes two pointer writes: the new node's next is set to the old head's address, and the tail's next is retargeted from the old head to the new one. Watch C's next cell below change from 0x1A to 0x4D — the loop has to be re-closed onto the new head.
↓ insert X at head ↓
- Create new node with data
- If list is empty, set head and tail to new node
- Make new node point to itself (circular reference)
- For non-empty list, set new node's next to current head
- Update tail's next pointer to new node
- Move head pointer to new node
Deletion Process
Deleting the head is the mirror image: head moves on to the next node, and the tail's next is retargeted onto that new head so the ring never breaks. Forgetting that second write is the classic bug — it leaves the tail pointing at a node that is no longer part of the list.
- Check if list is empty
- If single node exists, set head and tail to null
- For head deletion, update head to head.next
- Update tail's next pointer to new head
- For middle deletion, find node and update previous node's pointer
- Handle special case when deleting last node
↓ delete X (the head) ↓
Operation Walkthrough
A full sequence on an empty list, one operation at a time. Follow the last node's next cell — it is rewritten on every single operation, because whichever node ends up last is responsible for closing the loop:
Initialization — An empty list has nothing to loop through, so head is simply null.
insertFirst(10) — A single node is a complete loop on its own — its next holds its own address, 0x1A.
insertFirst(20) — 20's next points at 10, and 10 stops pointing at itself and points back at the new head instead.
insertFirst(30) — Again the tail's next is retargeted at the new head. Whichever node is last always closes the loop.
deleteFirst() — head moves to 20, and the tail's next is updated to 20's address so the loop stays intact.
delete(10) — One node is left, so it closes the loop by pointing at itself again.
Pros and Cons
Advantages
- Continuous traversal from any node
- Efficient round-robin scheduling
- No need for null checks during traversal
- Useful for circular buffer implementations
Limitations
- Risk of infinite loops if not handled carefully
- Slightly more complex implementation
- Harder to detect list boundaries
Comparison with Linear Linked List
| Feature | Linear | Circular |
|---|---|---|
| Structure | Linear with null termination | Circular with no null |
| Traversal | Stops at end | Continuous loop |
| Memory Overhead | Standard | Same as linear |
| Boundary Detection | Easy (null check) | Requires start reference |
| Insert/Delete at Head | O(1) | O(1) |
| Implementation Complexity | Simpler | More complex |
Applications
- Operating system round-robin scheduling
- Multiplayer turn-based games
- Music/video playlists with repeat functionality
- Resource allocation in networking
- Circular buffer implementations
- Token ring networks
When to Choose: Prefer circular linked lists when you need continuous cycling through elements or when the application naturally follows a circular pattern (like round-robin scheduling).