Circular Linked List: The Looping Chain Explained
A circular linked list is a linked list whose last node points back to the first node instead of to null, so the chain forms a closed loop. There is no “end” — following next forever cycles through the nodes endlessly. This shape is perfect when you need to cycle through items repeatedly, such as round-robin scheduling or a looping playlist. It can be built on top of a singly or doubly linked list.
How it differs
In a normal list the tail’s next is null. In a circular list the tail’s next is the head. A handy convention is to keep a single pointer to the tail: then tail->next is the head, giving O(1) access to both ends.
struct Node {
int data;
Node* next;
Node(int v) : data(v), next(nullptr) {}
};
// A single node points to itself: n->next == n
class Node {
int data;
Node next;
Node(int v) { data = v; next = null; }
}
// A single node points to itself: n.next == n
class Node {
constructor(value) {
this.data = value;
this.next = null;
}
}
// A single node points to itself: n.next === n
class Node:
def __init__(self, value):
self.data = value
self.next = None
# A single node points to itself: n.next is n
Traverse safely with a do-while pattern
You cannot loop “until null” — there is no null. Instead, remember where you started and stop when you return to it. A do-while style visits each node exactly once.
void printCircular(Node* head) {
if (head == nullptr) return;
Node* cur = head;
do {
std::cout << cur->data << " ";
cur = cur->next;
} while (cur != head);
}
void printCircular(Node head) {
if (head == null) return;
Node cur = head;
do {
System.out.print(cur.data + " ");
cur = cur.next;
} while (cur != head);
}
function printCircular(head) {
if (head === null) return;
let cur = head;
do {
console.log(cur.data);
cur = cur.next;
} while (cur !== head);
}
def print_circular(head):
if head is None:
return
cur = head
while True:
print(cur.data)
cur = cur.next
if cur is head:
break
Insert at the front (tail-pointer design)
Holding the tail, the head is tail->next. To push a new node to the front, splice it between the tail and the old head — the tail itself does not move.
Node* pushFront(Node* tail, int value) {
Node* node = new Node(value);
if (tail == nullptr) { // empty list
node->next = node; // points to itself
return node; // node is the only node, also the tail
}
node->next = tail->next; // new node -> old head
tail->next = node; // tail -> new head
return tail; // tail is unchanged
}
Node pushFront(Node tail, int value) {
Node node = new Node(value);
if (tail == null) { // empty list
node.next = node; // points to itself
return node;
}
node.next = tail.next; // new node -> old head
tail.next = node; // tail -> new head
return tail; // tail is unchanged
}
function pushFront(tail, value) {
const node = new Node(value);
if (tail === null) { // empty list
node.next = node; // points to itself
return node;
}
node.next = tail.next; // new node -> old head
tail.next = node; // tail -> new head
return tail; // tail is unchanged
}
def push_front(tail, value):
node = Node(value)
if tail is None: # empty list
node.next = node # points to itself
return node
node.next = tail.next # new node -> old head
tail.next = node # tail -> new head
return tail # tail is unchanged
Going deeper: With this tail-pointer design, push-back is identical to push-front but you also advance the tail to the new node (
return nodeinstead ofreturn tail). That gives O(1) insertion at both ends with a single stored pointer.
Complexity
| Operation | Time |
|---|---|
| Insert at front (tail pointer) | O(1) |
| Insert at back (tail pointer) | O(1) |
| Access head / tail | O(1) |
| Search / access by index | O(n) |
Pitfall: The number-one circular-list bug is an infinite loop — looping “until next is null” never terminates. Always anchor on the start node and stop when you come back to it. Handle the empty and single-node (self-pointing) cases explicitly.
When to use a circular linked list
Pick a circular linked list whenever processing should wrap around forever: round-robin CPU scheduling, turn-taking in a game, a repeating media playlist, or a ring buffer. If you never need to wrap, a plain singly or doubly list is simpler and less error-prone.
Next: tie it all together with the core linked list operations, then learn reversing a linked list.