Types

Circular Linked List

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

OperationComplexityDescription
Insertion at HeadO(1)Add new node at beginning, point last node to new head
Insertion at TailO(1)Add new node at end, point it to head (with tail pointer)
Deletion at HeadO(1)Remove first node, update last node's pointer
Deletion by ValueO(n)Traverse list to find and remove specific node
TraversalO(n)Loop through nodes until returning to starting point
SearchO(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:

headtail.next = head0x1AA0x2F0x2FB0x3C0x3CC0x1A

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:

ABChead

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.

headtail.next = head0x1AA0x2F0x2FB0x3C0x3CC0x1A

↓ insert X at head ↓

headtail.next = head0x4DX0x1A0x1AA0x2F0x2FB0x4D
  1. Create new node with data
  2. If list is empty, set head and tail to new node
  3. Make new node point to itself (circular reference)
  4. For non-empty list, set new node's next to current head
  5. Update tail's next pointer to new node
  6. 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.

  1. Check if list is empty
  2. If single node exists, set head and tail to null
  3. For head deletion, update head to head.next
  4. Update tail's next pointer to new head
  5. For middle deletion, find node and update previous node's pointer
  6. Handle special case when deleting last node
headtail.next = head0x4DX0x1A0x1AA0x2F0x2FB0x4D

↓ delete X (the head) ↓

headtail.next = head0x1AA0x2F0x2FB0x1A

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:

InitializationAn empty list has nothing to loop through, so head is simply null.

headnull

insertFirst(10)A single node is a complete loop on its own — its next holds its own address, 0x1A.

headtail.next = head0x1A100x1A

insertFirst(20)20's next points at 10, and 10 stops pointing at itself and points back at the new head instead.

headtail.next = head0x2F200x1A0x1A100x2F

insertFirst(30)Again the tail's next is retargeted at the new head. Whichever node is last always closes the loop.

headtail.next = head0x3C300x2F0x2F200x1A0x1A100x3C

deleteFirst()head moves to 20, and the tail's next is updated to 20's address so the loop stays intact.

headtail.next = head0x2F200x1A0x1A100x2F

delete(10)One node is left, so it closes the loop by pointing at itself again.

headtail.next = head0x2F200x2F

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

FeatureLinearCircular
StructureLinear with null terminationCircular with no null
TraversalStops at endContinuous loop
Memory OverheadStandardSame as linear
Boundary DetectionEasy (null check)Requires start reference
Insert/Delete at HeadO(1)O(1)
Implementation ComplexitySimplerMore 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).

Visualize Circular Linked List Operations

Empty List

Add nodes to visualize the circular linked list

Test Your Knowledge before moving forward!

Linked List Quiz Challenge

How it works:

  • +1 point for each correct answer
  • 0 points for wrong answers
  • -0.5 point penalty for viewing explanations
  • Earn stars based on your final score (max 5 stars)

Circular Linked List Implementation

// Circular Linked List Implementation in JavaScript
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

class CircularLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }

  // Insert at beginning
  insertFirst(data) {
    const newNode = new Node(data);
    if (!this.head) {
      this.head = newNode;
      this.tail = newNode;
      newNode.next = this.head; // Point to itself
    } else {
      newNode.next = this.head;
      this.head = newNode;
      this.tail.next = this.head; // Update tail's next to new head
    }
    this.size++;
  }

  // Insert at end
  insertLast(data) {
    const newNode = new Node(data);
    if (!this.head) {
      this.head = newNode;
      this.tail = newNode;
      newNode.next = this.head;
    } else {
      this.tail.next = newNode;
      newNode.next = this.head;
      this.tail = newNode;
    }
    this.size++;
  }

  // Insert at index
  insertAt(data, index) {
    if (index < 0 || index > this.size) return;
    if (index === 0) return this.insertFirst(data);
    if (index === this.size) return this.insertLast(data);

    const newNode = new Node(data);
    let current = this.head;
    let count = 0;

    while (count < index - 1) {
      current = current.next;
      count++;
    }

    newNode.next = current.next;
    current.next = newNode;
    this.size++;
  }

  // Remove from beginning
  removeFirst() {
    if (!this.head) return null;
    const removedNode = this.head;
    if (this.size === 1) {
      this.head = null;
      this.tail = null;
    } else {
      this.head = this.head.next;
      this.tail.next = this.head; // Update tail's next to new head
    }
    this.size--;
    return removedNode.data;
  }

  // Remove from end
  removeLast() {
    if (!this.head) return null;
    const removedNode = this.tail;
    if (this.size === 1) {
      this.head = null;
      this.tail = null;
    } else {
      let current = this.head;
      while (current.next !== this.tail) {
        current = current.next;
      }
      current.next = this.head; // Point new tail to head
      this.tail = current;
    }
    this.size--;
    return removedNode.data;
  }

  // Remove at index
  removeAt(index) {
    if (index < 0 || index >= this.size) return null;
    if (index === 0) return this.removeFirst();
    if (index === this.size - 1) return this.removeLast();

    let current = this.head;
    let count = 0;
    while (count < index - 1) {
      current = current.next;
      count++;
    }
    const removedNode = current.next;
    current.next = removedNode.next;
    this.size--;
    return removedNode.data;
  }

  // Get at index
  getAt(index) {
    if (index < 0 || index >= this.size) return null;
    let current = this.head;
    let count = 0;
    while (count < index) {
      current = current.next;
      count++;
    }
    return current.data;
  }

  // Clear list
  clear() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }

  // Print list
  print() {
    if (!this.head) {
      console.log("List is empty");
      return;
    }
    let current = this.head;
    let result = "";
    do {
      result += current.data + " -> ";
      current = current.next;
    } while (current !== this.head);
    result += "(head)";
    console.log(result);
  }

  // Check if list is circular
  isCircular() {
    if (!this.head) return true;
    let slow = this.head;
    let fast = this.head.next;
    while (fast && fast.next) {
      if (slow === fast) return true;
      slow = slow.next;
      fast = fast.next.next;
    }
    return false;
  }
}

// Usage Example
const cll = new CircularLinkedList();
cll.insertFirst(100);
cll.insertFirst(200);
cll.insertLast(300);
cll.insertAt(500, 1);
cll.print(); // 200 -> 500 -> 100 -> 300 -> (head)
cll.removeAt(2);
console.log(cll.getAt(1)); // 500
console.log("Is circular:", cll.isCircular()); // true

Done With the Learning

Mark Circular Linked List as done and view it on your dashboard