Circular Linked List Explained

Learn Circular Linked List Explained with practical examples, explanations, best practices, and real-world coding techniques.

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 !== null as 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.

Code Examples

function traverse(head) {
  if (head === null) {
    return;
  }

  let current = head;

  do {
    console.log(current.data);
    current = current.next;
  } while (current !== head);
}
    
function insertAtEnd(tail, data) {
  const newNode = {
    data,
    next: null
  };

  if (tail === null) {
    newNode.next = newNode;
    return newNode;
  }

  newNode.next = tail.next;
  tail.next = newNode;

  return newNode;
}
    
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;
}
    

Pro Tip

In a circular linked list, never assume the end is null. Choose a starting node and stop traversal when you reach that same node again.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.