Skip to content
  • Circular Linked List · DSA Basics

Circular Linked List

LearnViaNotes @learnvianotes· 22 min read

A circular linked list is a singly linked list whose last node points back to the head instead of to None. There is no end, so the list can be traversed from any node, and keeping a single pointer gives O(1) access to both the first and the last element.

10 → 5 → 20 → 15 → back to 10
head
 ↓
┌──────┬────┐    ┌──────┬────┐    ┌──────┬────┐    ┌──────┬────┐
│  10  │ ●──┼───▶│   5  │ ●──┼───▶│  20  │ ●──┼───▶│  15  │ ●──┼──┐
└──────┴────┘    └──────┴────┘    └──────┴────┘    └──────┴────┘  │
  ▲                                                               │
  └───────────────────────────────────────────────────────────────┘
                the last node links back to the head

Building and traversing

The nodes are the same as in a singly linked list; only the last link differs. Traversal must stop when it comes back to the head, so the loop prints the head first and then walks until curr == head again. A while curr.next != head loop (as in the original notes) also works but needs the last node printed separately.

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def print_circular(head):
    if head is None:
        print("empty")
        return
    parts = [str(head.data)]
    curr = head.next
    while curr != head:              # stop when we are back at the start
        parts.append(str(curr.data))
        curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10)
head.next = Node(5)
head.next.next = Node(20)
head.next.next.next = Node(15)
head.next.next.next.next = head      # close the circle
print_circular(head)
C++
#include <iostream>
using namespace std;

struct Node {
    int data; Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

void printCircular(Node* head) {
    if (!head) { cout << "empty\n"; return; }
    cout << head->data;
    for (Node* curr = head->next; curr != head; curr = curr->next)   // stop when back at the start
        cout << " -> " << curr->data;
    cout << " -> (back to " << head->data << ")\n";
}

int main() {
    Node* head = new Node(10);
    head->next = new Node(5);
    head->next->next = new Node(20);
    head->next->next->next = new Node(15);
    head->next->next->next->next = head;    // close the circle
    printCircular(head);
}
Java
public class Main {
    static class Node {
        int data; Node next;
        Node(int d) { data = d; }
    }

    static void printCircular(Node head) {
        if (head == null) { System.out.println("empty"); return; }
        StringBuilder sb = new StringBuilder().append(head.data);
        for (Node curr = head.next; curr != head; curr = curr.next)   // stop when back at the start
            sb.append(" -> ").append(curr.data);
        System.out.println(sb + " -> (back to " + head.data + ")");
    }

    public static void main(String[] args) {
        Node head = new Node(10);
        head.next = new Node(5);
        head.next.next = new Node(20);
        head.next.next.next = new Node(15);
        head.next.next.next.next = head;    // close the circle
        printCircular(head);
    }
}
JavaScript
class Node {
    constructor(data) { this.data = data; this.next = null; }
}

function printCircular(head) {
    if (head === null) { console.log("empty"); return; }
    const parts = [head.data];
    for (let curr = head.next; curr !== head; curr = curr.next)   // stop when back at the start
        parts.push(curr.data);
    console.log(parts.join(" -> "), `-> (back to ${head.data})`);
}

const head = new Node(10);
head.next = new Node(5);
head.next.next = new Node(20);
head.next.next.next = new Node(15);
head.next.next.next.next = head;        // close the circle
printCircular(head);
Output
10 -> 5 -> 20 -> 15 -> (back to 10)

Time O(n) traversal · Space O(1)

Advantages and disadvantages

Operation (holding head only)NaiveEfficient trickHolding tail instead
Insert at beginningO(n): walk to the last node to fix its nextO(1): insert after head and swap dataO(1)
Insert at endO(n): walk to the last nodeO(1): same trick, return the new node as headO(1)
Delete headO(n): walk to the last node to re-point itO(1): copy head.next into head and unlink head.nextO(1)
Delete k-th nodeO(k)—O(k)
SearchO(n)—O(n)

Insert at the beginning

Naive, O(n). The last node must point at the new head, and the only way to reach the last node is to walk the whole circle.

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def insert_beginning_naive(head, x):
    temp = Node(x)
    if head is None:
        temp.next = temp               # one node points at itself
        return temp
    curr = head
    while curr.next != head:           # O(n) walk to the last node
        curr = curr.next
    curr.next = temp                   # last node → new node
    temp.next = head                   # new node → old head
    return temp

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
head = insert_beginning_naive(head, 25)
print_circular(head)
Output
25 -> 10 -> 5 -> 20 -> 15 -> (back to 25)

Time O(n) · Space O(1)

Insert at the beginning in O(1): insert after the head, then swap the two values
before:          head ──▶ [10] ──▶ [5] ──▶ [20] ──▶ [15] ──▶ back to 10
insert after head:   head ──▶ [10] ──▶ [25] ──▶ [5] ──▶ [20] ──▶ [15] ──▶ back to 10
swap data:           head ──▶ [25] ──▶ [10] ──▶ [5] ──▶ [20] ──▶ [15] ──▶ back to 25
Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def insert_beginning(head, x):
    temp = Node(x)
    if head is None:
        temp.next = temp                             # one node points at itself
        return temp
    temp.next = head.next                            # insert temp right after head
    head.next = temp
    head.data, temp.data = temp.data, head.data      # swap values: head now holds x
    return head

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
head = insert_beginning(head, 25); print_circular(head)
head = insert_beginning(head, 30); print_circular(head)
C++
#include <iostream>
#include <utility>
using namespace std;

struct Node {
    int data; Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

Node* insertBeginning(Node* head, int x) {
    Node* temp = new Node(x);
    if (!head) { temp->next = temp; return temp; }   // one node points at itself
    temp->next = head->next;                         // insert temp right after head
    head->next = temp;
    swap(head->data, temp->data);                    // swap values: head now holds x
    return head;
}

void printCircular(Node* head) {
    cout << head->data;
    for (Node* curr = head->next; curr != head; curr = curr->next) cout << " -> " << curr->data;
    cout << " -> (back to " << head->data << ")\n";
}

int main() {
    Node* head = new Node(10); head->next = new Node(5); head->next->next = new Node(20);
    head->next->next->next = new Node(15); head->next->next->next->next = head;
    head = insertBeginning(head, 25); printCircular(head);
    head = insertBeginning(head, 30); printCircular(head);
}
Java
public class Main {
    static class Node {
        int data; Node next;
        Node(int d) { data = d; }
    }

    static Node insertBeginning(Node head, int x) {
        Node temp = new Node(x);
        if (head == null) { temp.next = temp; return temp; }   // one node points at itself
        temp.next = head.next;                                  // insert temp right after head
        head.next = temp;
        int t = head.data; head.data = temp.data; temp.data = t; // swap values: head now holds x
        return head;
    }

    static void printCircular(Node head) {
        StringBuilder sb = new StringBuilder().append(head.data);
        for (Node curr = head.next; curr != head; curr = curr.next) sb.append(" -> ").append(curr.data);
        System.out.println(sb + " -> (back to " + head.data + ")");
    }

    public static void main(String[] args) {
        Node head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
        head.next.next.next = new Node(15); head.next.next.next.next = head;
        head = insertBeginning(head, 25); printCircular(head);
        head = insertBeginning(head, 30); printCircular(head);
    }
}
JavaScript
class Node {
    constructor(data) { this.data = data; this.next = null; }
}

function insertBeginning(head, x) {
    const temp = new Node(x);
    if (head === null) { temp.next = temp; return temp; }   // one node points at itself
    temp.next = head.next;                                   // insert temp right after head
    head.next = temp;
    [head.data, temp.data] = [temp.data, head.data];         // swap values: head now holds x
    return head;
}

function printCircular(head) {
    const parts = [head.data];
    for (let curr = head.next; curr !== head; curr = curr.next) parts.push(curr.data);
    console.log(parts.join(" -> "), `-> (back to ${head.data})`);
}

let head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
head.next.next.next = new Node(15); head.next.next.next.next = head;
head = insertBeginning(head, 25); printCircular(head);
head = insertBeginning(head, 30); printCircular(head);
Output
25 -> 10 -> 5 -> 20 -> 15 -> (back to 25)
30 -> 25 -> 10 -> 5 -> 20 -> 15 -> (back to 30)

Time O(1) · Space O(1)

Insert at the end

Naive, O(n). Walk to the last node, link it to the new node and link the new node back to the head.

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def insert_end_naive(head, x):
    temp = Node(x)
    if head is None:
        temp.next = temp
        return temp
    curr = head
    while curr.next != head:           # O(n) walk to the last node
        curr = curr.next
    curr.next = temp                   # last → new
    temp.next = head                   # new → head
    return head

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
head = insert_end_naive(head, 35)
print_circular(head)
Output
10 -> 5 -> 20 -> 15 -> 35 -> (back to 10)

Time O(n) · Space O(1)

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def insert_end(head, x):
    temp = Node(x)
    if head is None:
        temp.next = temp
        return temp
    temp.next = head.next                            # insert temp right after head
    head.next = temp
    head.data, temp.data = temp.data, head.data      # old head value moves to temp
    return temp                                      # temp is the new head; x sits just before it

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
head = insert_end(head, 35); print_circular(head)
head = insert_end(head, 40); print_circular(head)
C++
#include <iostream>
#include <utility>
using namespace std;

struct Node {
    int data; Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

Node* insertEnd(Node* head, int x) {
    Node* temp = new Node(x);
    if (!head) { temp->next = temp; return temp; }
    temp->next = head->next;                         // insert temp right after head
    head->next = temp;
    swap(head->data, temp->data);                    // old head value moves to temp
    return temp;                                     // temp is the new head; x sits just before it
}

void printCircular(Node* head) {
    cout << head->data;
    for (Node* curr = head->next; curr != head; curr = curr->next) cout << " -> " << curr->data;
    cout << " -> (back to " << head->data << ")\n";
}

int main() {
    Node* head = new Node(10); head->next = new Node(5); head->next->next = new Node(20);
    head->next->next->next = new Node(15); head->next->next->next->next = head;
    head = insertEnd(head, 35); printCircular(head);
    head = insertEnd(head, 40); printCircular(head);
}
Java
public class Main {
    static class Node {
        int data; Node next;
        Node(int d) { data = d; }
    }

    static Node insertEnd(Node head, int x) {
        Node temp = new Node(x);
        if (head == null) { temp.next = temp; return temp; }
        temp.next = head.next;                                   // insert temp right after head
        head.next = temp;
        int t = head.data; head.data = temp.data; temp.data = t;  // old head value moves to temp
        return temp;                                             // temp is the new head; x sits just before it
    }

    static void printCircular(Node head) {
        StringBuilder sb = new StringBuilder().append(head.data);
        for (Node curr = head.next; curr != head; curr = curr.next) sb.append(" -> ").append(curr.data);
        System.out.println(sb + " -> (back to " + head.data + ")");
    }

    public static void main(String[] args) {
        Node head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
        head.next.next.next = new Node(15); head.next.next.next.next = head;
        head = insertEnd(head, 35); printCircular(head);
        head = insertEnd(head, 40); printCircular(head);
    }
}
JavaScript
class Node {
    constructor(data) { this.data = data; this.next = null; }
}

function insertEnd(head, x) {
    const temp = new Node(x);
    if (head === null) { temp.next = temp; return temp; }
    temp.next = head.next;                                   // insert temp right after head
    head.next = temp;
    [head.data, temp.data] = [temp.data, head.data];         // old head value moves to temp
    return temp;                                             // temp is the new head; x sits just before it
}

function printCircular(head) {
    const parts = [head.data];
    for (let curr = head.next; curr !== head; curr = curr.next) parts.push(curr.data);
    console.log(parts.join(" -> "), `-> (back to ${head.data})`);
}

let head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
head.next.next.next = new Node(15); head.next.next.next.next = head;
head = insertEnd(head, 35); printCircular(head);
head = insertEnd(head, 40); printCircular(head);
Output
10 -> 5 -> 20 -> 15 -> 35 -> (back to 10)
10 -> 5 -> 20 -> 15 -> 35 -> 40 -> (back to 10)

Time O(1) · Space O(1)

Delete the head

Naive, O(n). The last node points at the head, so after removing the head the last node must be found and re-pointed at the second node — a full walk.

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def delete_head_naive(head):
    if head is None or head.next == head:   # empty or single node → empty
        return None
    curr = head
    while curr.next != head:                # O(n) walk to the last node
        curr = curr.next
    curr.next = head.next                   # last → second node
    return curr.next                        # second node is the new head

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
head = delete_head_naive(head)
print_circular(head)
Output
5 -> 20 -> 15 -> (back to 5)

Time O(n) · Space O(1)

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def delete_head(head):
    if head is None or head.next == head:   # empty or single node → empty
        return None
    head.data = head.next.data               # pull the second value into the head node
    head.next = head.next.next               # unlink the second node
    return head

def print_circular(head):
    if head is None:
        print("empty"); return
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
for _ in range(4):
    head = delete_head(head)
    print_circular(head)
C++
#include <iostream>
using namespace std;

struct Node {
    int data; Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

Node* deleteHead(Node* head) {
    if (!head) return nullptr;
    if (head->next == head) { delete head; return nullptr; }   // single node → empty
    Node* second = head->next;
    head->data = second->data;               // pull the second value into the head node
    head->next = second->next;               // unlink the second node
    delete second;
    return head;
}

void printCircular(Node* head) {
    if (!head) { cout << "empty\n"; return; }
    cout << head->data;
    for (Node* curr = head->next; curr != head; curr = curr->next) cout << " -> " << curr->data;
    cout << " -> (back to " << head->data << ")\n";
}

int main() {
    Node* head = new Node(10); head->next = new Node(5); head->next->next = new Node(20);
    head->next->next->next = new Node(15); head->next->next->next->next = head;
    for (int i = 0; i < 4; i++) {
        head = deleteHead(head);
        printCircular(head);
    }
}
Java
public class Main {
    static class Node {
        int data; Node next;
        Node(int d) { data = d; }
    }

    static Node deleteHead(Node head) {
        if (head == null || head.next == head) return null;   // empty or single node → empty
        head.data = head.next.data;              // pull the second value into the head node
        head.next = head.next.next;              // unlink the second node
        return head;
    }

    static void printCircular(Node head) {
        if (head == null) { System.out.println("empty"); return; }
        StringBuilder sb = new StringBuilder().append(head.data);
        for (Node curr = head.next; curr != head; curr = curr.next) sb.append(" -> ").append(curr.data);
        System.out.println(sb + " -> (back to " + head.data + ")");
    }

    public static void main(String[] args) {
        Node head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
        head.next.next.next = new Node(15); head.next.next.next.next = head;
        for (int i = 0; i < 4; i++) {
            head = deleteHead(head);
            printCircular(head);
        }
    }
}
JavaScript
class Node {
    constructor(data) { this.data = data; this.next = null; }
}

function deleteHead(head) {
    if (head === null || head.next === head) return null;    // empty or single node → empty
    head.data = head.next.data;                  // pull the second value into the head node
    head.next = head.next.next;                  // unlink the second node
    return head;
}

function printCircular(head) {
    if (head === null) { console.log("empty"); return; }
    const parts = [head.data];
    for (let curr = head.next; curr !== head; curr = curr.next) parts.push(curr.data);
    console.log(parts.join(" -> "), `-> (back to ${head.data})`);
}

let head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
head.next.next.next = new Node(15); head.next.next.next.next = head;
for (let i = 0; i < 4; i++) {
    head = deleteHead(head);
    printCircular(head);
}
Output
5 -> 20 -> 15 -> (back to 5)
20 -> 15 -> (back to 20)
15 -> (back to 15)
empty

Time O(1) · Space O(1)

Delete the k-th node

  1. Empty list

    Return None.

  2. k == 1

    Return delete_head(head) — the O(1) version above.

  3. Walk

    curr = head; move k - 2 times → curr is the (k-1)-th node.

  4. Bypass

    curr.next = curr.next.next removes the k-th node.

Python
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def delete_head(head):
    if head is None or head.next == head:
        return None
    head.data = head.next.data
    head.next = head.next.next
    return head

def delete_kth(head, k):
    if head is None:
        return None
    if k == 1:
        return delete_head(head)
    curr = head
    for _ in range(k - 2):                   # k-2 steps reach the (k-1)-th node
        curr = curr.next
    curr.next = curr.next.next               # bypass the k-th node
    return head

def print_circular(head):
    parts, curr = [str(head.data)], head.next
    while curr != head:
        parts.append(str(curr.data)); curr = curr.next
    print(" -> ".join(parts), f"-> (back to {head.data})")

head = Node(10); head.next = Node(5); head.next.next = Node(20); head.next.next.next = Node(15)
head.next.next.next.next = head
print_circular(head)
for k, label in ((2, "2nd"), (3, "3rd"), (1, "1st")):
    head = delete_kth(head, k)
    print(f"delete {label}: ", end=""); print_circular(head)
C++
#include <iostream>
using namespace std;

struct Node {
    int data; Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

Node* deleteHead(Node* head) {
    if (!head) return nullptr;
    if (head->next == head) { delete head; return nullptr; }
    Node* second = head->next;
    head->data = second->data; head->next = second->next;
    delete second;
    return head;
}

Node* deleteKth(Node* head, int k) {
    if (!head) return nullptr;
    if (k == 1) return deleteHead(head);
    Node* curr = head;
    for (int i = 0; i < k - 2; i++) curr = curr->next;   // k-2 steps reach the (k-1)-th node
    Node* victim = curr->next;
    curr->next = victim->next;                           // bypass the k-th node
    delete victim;
    return head;
}

void printCircular(Node* head) {
    cout << head->data;
    for (Node* curr = head->next; curr != head; curr = curr->next) cout << " -> " << curr->data;
    cout << " -> (back to " << head->data << ")\n";
}

int main() {
    Node* head = new Node(10); head->next = new Node(5); head->next->next = new Node(20);
    head->next->next->next = new Node(15); head->next->next->next->next = head;
    printCircular(head);
    int ks[] = {2, 3, 1}; const char* labels[] = {"2nd", "3rd", "1st"};
    for (int i = 0; i < 3; i++) {
        head = deleteKth(head, ks[i]);
        cout << "delete " << labels[i] << ": "; printCircular(head);
    }
}
Java
public class Main {
    static class Node {
        int data; Node next;
        Node(int d) { data = d; }
    }

    static Node deleteHead(Node head) {
        if (head == null || head.next == head) return null;
        head.data = head.next.data; head.next = head.next.next;
        return head;
    }

    static Node deleteKth(Node head, int k) {
        if (head == null) return null;
        if (k == 1) return deleteHead(head);
        Node curr = head;
        for (int i = 0; i < k - 2; i++) curr = curr.next;   // k-2 steps reach the (k-1)-th node
        curr.next = curr.next.next;                         // bypass the k-th node
        return head;
    }

    static void printCircular(Node head) {
        StringBuilder sb = new StringBuilder().append(head.data);
        for (Node curr = head.next; curr != head; curr = curr.next) sb.append(" -> ").append(curr.data);
        System.out.println(sb + " -> (back to " + head.data + ")");
    }

    public static void main(String[] args) {
        Node head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
        head.next.next.next = new Node(15); head.next.next.next.next = head;
        printCircular(head);
        int[] ks = {2, 3, 1}; String[] labels = {"2nd", "3rd", "1st"};
        for (int i = 0; i < 3; i++) {
            head = deleteKth(head, ks[i]);
            System.out.print("delete " + labels[i] + ": "); printCircular(head);
        }
    }
}
JavaScript
class Node {
    constructor(data) { this.data = data; this.next = null; }
}

function deleteHead(head) {
    if (head === null || head.next === head) return null;
    head.data = head.next.data; head.next = head.next.next;
    return head;
}

function deleteKth(head, k) {
    if (head === null) return null;
    if (k === 1) return deleteHead(head);
    let curr = head;
    for (let i = 0; i < k - 2; i++) curr = curr.next;   // k-2 steps reach the (k-1)-th node
    curr.next = curr.next.next;                         // bypass the k-th node
    return head;
}

function printCircular(head) {
    const parts = [head.data];
    for (let curr = head.next; curr !== head; curr = curr.next) parts.push(curr.data);
    console.log(parts.join(" -> "), `-> (back to ${head.data})`);
}

let head = new Node(10); head.next = new Node(5); head.next.next = new Node(20);
head.next.next.next = new Node(15); head.next.next.next.next = head;
printCircular(head);
for (const [k, label] of [[2, "2nd"], [3, "3rd"], [1, "1st"]]) {
    head = deleteKth(head, k);
    process.stdout.write(`delete ${label}: `); printCircular(head);
}
Output
10 -> 5 -> 20 -> 15 -> (back to 10)
delete 2nd: 10 -> 20 -> 15 -> (back to 10)
delete 3rd: 10 -> 20 -> (back to 10)
delete 1st: 20 -> (back to 20)

Time O(k) · Space O(1)

Interview Q&A

How do you detect that a linked list is circular?

Why keep a tail pointer rather than a head pointer in a circular list?

Where are circular linked lists used in practice?

What goes wrong if you forget the single-node case?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report