What Is a Circular Linked List?
A circular linked list is a linked list in which the last node points back to the first node
instead of pointing to null. This creates a continuous circular structure where
traversal can continue from the last node back to the beginning.
In a standard singly linked list, the final node has a next reference of
null. In a circular linked list, the final node's next reference
points to the head.
Circular Linked List Structure
Consider a list containing three nodes:
10 → 20 → 30
↑ ↓
└─────────┘
The node containing 30 does not point to null. Instead, its
next reference points back to the node containing 10.
head
↓
10 → 20 → 30
↑ |
└─────────┘
Because there is no null at the end, traversal must use a different stopping
condition.
Circular vs Standard Singly Linked List
The main structural difference is what the final node points to.
| Feature | Singly Linked List | Circular Linked List |
|---|---|---|
| Last node | Points to null | Points to head |
| Natural stopping point | null | Head is reached again |
| Traversal | Ends at the last node | Can continue around the cycle |
| Typical use | Linear sequences | Repeated or cyclic sequences |
Types of Circular Linked Lists
Circular linked lists can be implemented using either one or two links per node.
Circular Singly Linked List
Each node contains a data value and a next reference. The last node points
back to the first node.
10 → 20 → 30
↑ ↓
└─────────┘
Circular Doubly Linked List
Each node contains both prev and next references. The last node's
next points to the head, while the head's prev points to the
last node.
┌─────────────────────┐
↓ │
10 ⇄ 20 ⇄ 30 ⇄ 40
↑ │
└─────────────────────┘
This topic focuses primarily on the circular singly linked list because it demonstrates the core circular linked list concept.
Creating a Circular Linked List Node
A circular singly linked list uses the same basic node structure as a singly linked list. The circular behavior comes from how the nodes are connected.
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
When the first node is created, a single-node circular list can point back to itself.
const node = new Node(10);
node.next = node;
The structure is:
10
↖ ↓
└───
Creating a Circular Linked List
Multiple nodes can be connected so that the final node points back to the head.
const first = new Node(10);
const second = new Node(20);
const third = new Node(30);
first.next = second;
second.next = third;
third.next = first;
The resulting structure is:
10 → 20 → 30
↑ ↓
└─────────┘
Traversing a Circular Linked List
Traversal is one of the most important differences between a circular linked list and a
standard singly linked list. You cannot use current !== null as the stopping
condition because the list never reaches null.
Instead, start from the head and stop when the traversal reaches the head again.
function traverse(head) {
if (head === null) {
return;
}
let current = head;
do {
console.log(current.data);
current = current.next;
} while (current !== head);
}
A do...while loop works naturally here because the head should be processed at
least once when the list is not empty.
Why a Normal While Loop Can Be Dangerous
This pattern works for a standard singly linked list:
while (current !== null) {
current = current.next;
}
But a circular linked list does not reach null. The loop can continue
indefinitely. A circular list therefore requires a condition based on returning to the
starting node or another explicit traversal limit.
Using a Tail Reference
A circular singly linked list can be efficiently managed by storing a reference to the tail instead of explicitly storing a separate head reference.
In this design, the head is always available as tail.next.
tail
↓
30 → 10 → 20
↑
head
This representation makes insertion at the beginning and end particularly convenient.
Insertion at the Beginning
When a tail reference is maintained, the head is tail.next. To insert a new
node at the beginning, connect the new node to the current head and then make it the new
head.
const newNode = new Node(5);
newNode.next = tail.next;
tail.next = newNode;
Before insertion:
10 → 20 → 30
↑ ↓
└─────────┘
After inserting 5:
5 → 10 → 20 → 30
↑ ↓
└──────────────┘
Because the tail is already known, insertion at the beginning takes O(1)
time.
Insertion at the End
Inserting at the end is also efficient when a tail reference is maintained. The new node should point to the head, and the old tail should point to the new node. The new node then becomes the tail.
const newNode = new Node(40);
newNode.next = tail.next;
tail.next = newNode;
tail = newNode;
The resulting list remains circular:
10 → 20 → 30 → 40
↑ ↓
└──────────────┘
Deleting the First Node
When the list contains multiple nodes, deleting the first node means changing the tail's
next reference so that it points to the second node.
tail.next = tail.next.next;
If the list contains only one node, the node points to itself. In that case, deleting the
node should make the tail null.
if (tail.next === tail) {
tail = null;
}
Deleting the Last Node
In a circular singly linked list, knowing the tail does not directly provide the node before the tail. Therefore, deleting the last node generally requires traversing the list to find the predecessor of the tail.
let current = tail.next;
while (current.next !== tail) {
current = current.next;
}
current.next = tail.next;
tail = current;
This operation takes O(n) time because the predecessor of the tail may need to
be found by traversal.
Searching a Circular Linked List
Searching follows the same general traversal idea, but the algorithm must stop when it reaches the starting node again.
function search(head, target) {
if (head === null) {
return false;
}
let current = head;
do {
if (current.data === target) {
return true;
}
current = current.next;
} while (current !== head);
return false;
}
The algorithm checks every node at most once before returning to the head.
Counting Nodes
Counting nodes in a circular linked list also requires stopping when the starting node is reached again.
function countNodes(head) {
if (head === null) {
return 0;
}
let count = 0;
let current = head;
do {
count++;
current = current.next;
} while (current !== head);
return count;
}
Complete Circular Linked List Implementation
The following implementation uses a tail reference. The head is always available through
tail.next.
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
class CircularLinkedList {
constructor() {
this.tail = null;
this.size = 0;
}
get head() {
return this.tail === null ? null : this.tail.next;
}
insertAtBeginning(data) {
const newNode = new Node(data);
if (this.tail === null) {
newNode.next = newNode;
this.tail = newNode;
} else {
newNode.next = this.tail.next;
this.tail.next = newNode;
}
this.size++;
}
insertAtEnd(data) {
const newNode = new Node(data);
if (this.tail === null) {
newNode.next = newNode;
this.tail = newNode;
} else {
newNode.next = this.tail.next;
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
}
deleteFirst() {
if (this.tail === null) {
return;
}
if (this.tail.next === this.tail) {
this.tail = null;
} else {
this.tail.next = this.tail.next.next;
}
this.size--;
}
deleteLast() {
if (this.tail === null) {
return;
}
if (this.tail.next === this.tail) {
this.tail = null;
this.size--;
return;
}
let current = this.tail.next;
while (current.next !== this.tail) {
current = current.next;
}
current.next = this.tail.next;
this.tail = current;
this.size--;
}
search(target) {
if (this.tail === null) {
return false;
}
let current = this.tail.next;
do {
if (current.data === target) {
return true;
}
current = current.next;
} while (current !== this.tail.next);
return false;
}
print() {
if (this.tail === null) {
console.log('Empty list');
return;
}
const values = [];
let current = this.tail.next;
do {
values.push(current.data);
current = current.next;
} while (current !== this.tail.next);
console.log(values.join(' → '));
}
}
const list = new CircularLinkedList();
list.insertAtEnd(10);
list.insertAtEnd(20);
list.insertAtEnd(30);
list.insertAtBeginning(5);
list.print();
console.log(list.search(20));
console.log(list.search(50));
list.deleteFirst();
list.deleteLast();
list.print();
Understanding the Tail-Based Design
Using only a tail reference may initially look unusual, but it provides a useful relationship:
tail.next === head
This means both ends of the circular list can be accessed efficiently. The tail is stored
directly, while the head is obtained from tail.next.
This representation is especially useful when a program frequently inserts elements at both ends of a circular sequence.
Circular Linked List Complexity
| Operation | Time Complexity | Reason |
|---|---|---|
| Insert at beginning | O(1) | Tail directly identifies the head through tail.next |
| Insert at end | O(1) | Tail is directly available |
| Delete first | O(1) | Head is tail.next |
| Delete last | O(n) | Previous node must be found |
| Search | O(n) | Nodes may need to be traversed |
| Traversal | O(n) | Each node is visited once |
Advantages of Circular Linked Lists
- The last node can connect directly back to the first node.
- Traversal can naturally continue from the end to the beginning.
- Efficient insertion at both ends is possible with a tail reference.
- Useful for applications that repeatedly cycle through elements.
- A tail-based representation provides access to the head through
tail.next.
Limitations of Circular Linked Lists
- Traversal requires a different stopping condition than a standard linked list.
- An incorrect loop condition can cause an infinite loop.
- Deleting the last node in a circular singly linked list still requires finding its predecessor.
- Reference errors can be harder to detect because there is no natural null endpoint.
- Random access by index is still not available.
Common Applications
Circular linked lists are useful when data needs to be processed repeatedly in a cycle. Instead of reaching an endpoint and stopping, traversal can return to the beginning and continue.
- Round-robin scheduling.
- Turn-based systems.
- Rotating task queues.
- Repeated playlist or media rotation.
- CPU scheduling concepts.
- Circular buffers and queue-related designs.
Circular Linked List and Round-Robin Scheduling
A common conceptual example is round-robin scheduling. Imagine several tasks taking turns:
Task A → Task B → Task C → Task A → ...
A circular structure naturally represents this repeated sequence because the final task points back to the first task.
Detecting the End of Traversal
Since a circular linked list does not terminate at null, traversal normally
compares the current node with the starting node.
const start = head;
let current = head;
do {
// Process current node
current = current.next;
} while (current !== start);
This guarantees that every node is visited once before the traversal stops.
Common Circular Linked List Mistakes
- Using
current !== nullas the only traversal condition. - Forgetting that the final node must point back to the head.
- Breaking the circular connection during insertion.
- Failing to handle the single-node case.
- Forgetting to update the tail when inserting at the end.
- Deleting the last node without finding its predecessor.
- Creating an accidental infinite loop with an incorrect stopping condition.
A Reliable Circular Traversal Pattern
When traversing a circular linked list, remember that the starting node is the boundary. Process the starting node, move through the list, and stop when the starting node is reached again.
let current = head;
if (current !== null) {
do {
// Process current
current = current.next;
} while (current !== head);
}
This pattern can be adapted for searching, counting, printing, validation, and other circular linked list operations.
Circular Linked List in Interviews
Circular linked lists are commonly used in interview questions involving cyclic traversal, scheduling, Josephus-style problems, insertion and deletion around a cycle, and detecting whether a linked structure contains a cycle.
When solving a circular linked list problem, first determine what node marks the beginning
of one complete cycle. Then make sure your traversal stops after returning to that node
rather than waiting for null.
Key Takeaways
- A circular linked list has no null reference at the end of its cycle.
- The last node points back to the first node.
- A tail reference can provide efficient access to both the tail and head.
- Insertion at the beginning and end can be O(1) with a tail-based design.
- Deleting the first node can be O(1), while deleting the last node is generally O(n) in a circular singly linked list.
- Traversal should stop when the starting node is reached again.
- Incorrect stopping conditions can result in infinite loops.
- Circular linked lists are useful for repeated or round-robin processing.