Linked Lists5 min read

Circular Linked List

A chain with no end — the last link points back to the first
access: \(O(n)\)insert head: \(O(1)\)insert tail: \(O(1)\)traverse full circle: \(O(n)\)space: \(O(n)\)

Picture a circular paper chain hanging as a decoration. Unlike a regular chain that starts and ends somewhere, a circular chain loops back on itself — there's no beginning and no end, just an endless loop.

A circular linked list works the same way. It's a linked list where the last node's pointer, instead of saying null, points back to the first node. The chain forms a complete ring.

You typically keep a single reference to any one node (often called head or just the current node). From there you can visit every node by following next pointers in a circle. There's no hard stop — you keep going until you've made a full loop (usually detected by returning to the node you started from).

How does insertion work?

To insert at the head of a circular list:

  1. Create the new node and point its next at the current head.
  2. Walk to the tail (the node whose next currently points to the old head).
  3. Update the tail's next to point at the new node.
  4. Move head to the new node.

If you also track a tail pointer, step 2 is \(O(1)\) — making the entire insertion \(O(1)\). With just a head pointer, step 2 costs \(O(n)\).

Explore circular list

← → arrow keys to step · click elements to interact

Circular Linked List: Insert & Traverse

class Node:
def __init__(self, val):
self.val = val
self.next = None
class CircularLinkedList:
def __init__(self): self.tail = None
def append(self, val):
n = Node(val)
if not self.tail:
n.next = n # points to itself
self.tail = n
else:
n.next = self.tail.next # new node -> head
self.tail.next = n # old tail -> new node
self.tail = n # update tail
def to_list(self, limit=None):
if not self.tail: return []
result, cur = [], self.tail.next # start from head
count = 0
while True:
result.append(cur.val)
cur = cur.next; count += 1
if cur == self.tail.next or (limit and count >= limit):
break
return result
cll = CircularLinkedList()
for v in [1, 2, 3, 4]: cll.append(v)
print(cll.to_list()) # [1, 2, 3, 4]
Output
[1, 2, 3, 4]
Note: The biggest danger with a circular linked list: forgetting that it loops. A naive traversal with a simple null-check will spin forever. Always detect the full circle by checking if you've returned to your starting node.

Why would you ever want a circular list?

The loop structure is exactly what certain problems need:

  • Round-robin scheduling — an operating system cycles through processes endlessly. A circular list is the perfect structure: after the last process, wrap back to the first without any special-casing.
  • Music playlists on repeat — when the last song finishes, go back to the first. A circular list models this naturally with no extra logic.
  • Multiplayer turn-based games — players take turns in a loop. When the last player finishes their turn, the next is the first player again.
  • Buffer rings — network packet buffers and audio buffers often use circular arrays or lists so that writing wraps around to the beginning without allocation.

Detecting the full circle

When traversing, start at any node and stop when you return to it:

pseudocode
1
2
3
4
5
current = head
do:
visit(current)
current = current.next
while current != head

This is a do-while loop — visit the starting node once, then keep going until you return to it.

Circular vs. regular linked list

The only structural difference is the last node's pointer. Everything else — node structure, most insertion/deletion logic — is identical. The big practical difference is in traversal: you must always guard against infinite loops by tracking your starting point.

Complexity

Access by positionMust traverse from start node
Slow\(O(n)\)
Insert at head (with tail ref)Tail's next updated to new head
Instant\(O(1)\)
Insert at tail (with tail ref)New tail points back to head
Instant\(O(1)\)
Full traversalVisit every node exactly once
Slow\(O(n)\)
SpaceOne node per element, plus one pointer overhead
Grows with n\(O(n)\)

Josephus Problem (Circular List)

def josephus(n, k):
"""Simulate: n people in circle, every k-th is eliminated. Return survivor index (0-based)."""
pos = 0
for i in range(2, n + 1):
pos = (pos + k) % i
return pos
# 7 people, every 3rd eliminated
print(josephus(7, 3)) # 3 (0-indexed survivor)
Output
3
Challenge

Quick check

In a circular linked list, what does the last node's next pointer point to?

Continue reading

Singly Linked List
A chain of boxes — each one knows only where the next box is
Doubly Linked List
A two-way chain — each node knows both its neighbour ahead and the one behind
Floyd's Cycle Detection
Catch a loop in a linked list using just two pointers and no extra memory