tail.next Wraps Back to Head — Endless Traversal With a Termination Guard
Circular Linked List
Learn how setting tail.next equal to head forms a circular linked list, why it suits round-robin scheduling, and how to traverse without looping forever.
What you'll learn
- Construct a circular linked list and maintain the tail-to-head link on insert and remove
- Traverse a circular list safely using a node-count or sentinel comparison
- Explain round-robin scheduling as a real-world application of circular lists
A circular linked list is a singly (or doubly) linked list where the last
node’s next points back to the first node instead of null. The list has
no natural end — you can spin around it indefinitely.
Node Class
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
} Building a Circular List
The list tracks only the tail. Because tail.next === head, you can
reach the head in O(1) without storing a separate head pointer.
class CircularLinkedList {
constructor() {
this.tail = null;
this.size = 0;
}
append(val) {
const node = new ListNode(val);
if (!this.tail) {
node.next = node; // single-node circle points to itself
this.tail = node;
} else {
node.next = this.tail.next; // new node points to current head
this.tail.next = node; // old tail points to new node
this.tail = node; // advance tail
}
this.size++;
}
get head() {
return this.tail ? this.tail.next : null;
}
} Safe Traversal — Count-Based Termination
Never use while (curr !== null) — there is no null in this list. Use a
counter instead.
toArray() {
if (!this.tail) return [];
const result = [];
let curr = this.head;
for (let i = 0; i < this.size; i++) {
result.push(curr.val);
curr = curr.next;
}
return result;
} Alternatively, stop when you lap back to the starting node:
forEach(fn) {
if (!this.tail) return;
const start = this.head;
let curr = start;
do {
fn(curr.val);
curr = curr.next;
} while (curr !== start);
} Round-Robin Scheduling
Operating systems and task schedulers use a circular list to give every
process a fair share of CPU time. After each time slice the scheduler
advances the current pointer one step; when it reaches the “end” it wraps
back to the first process automatically — no special wrap-around code needed.
class RoundRobinScheduler {
constructor(processes) {
this.list = new CircularLinkedList();
for (const p of processes) this.list.append(p);
this.current = this.list.head;
}
next() {
const proc = this.current.val;
this.current = this.current.next; // wraps automatically
return proc;
}
}
const scheduler = new RoundRobinScheduler(["P1", "P2", "P3"]);
console.log(scheduler.next()); // "P1"
console.log(scheduler.next()); // "P2"
console.log(scheduler.next()); // "P3"
console.log(scheduler.next()); // "P1" ← wrapped Up Next
Reverse the direction of a linked list in-place using three iterative pointers or a recursive approach — both in O(n) time and O(1) extra space.
Reverse a Linked List →