Circular Linked List

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.

4 min read Level 2/5 #dsa#linked-list#circular-linked-list
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 →