Skip to Content
Course content

149: Implementing a Circular Linked List

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once worked with a junior developer who was building a simple turn-based combat system for a tabletop RPG. He used a standard singly linked list to keep track of the players' turn order. Every time the turn passed to the last player, he had to write a clunky if (current == NULL) { current = head; } block to reset the loop. It worked, but he was fighting the data structure. I told him he was manually doing what the memory layout should be doing for him. That's where a circular linked list comes in—it turns that "reset" logic into a natural property of the list itself.

The Advantage of the Tail Pointer

When you first think of a circular linked list, your instinct is probably to keep a pointer to the head. But here is a pro tip: keep a pointer to the tail instead. Why? Because in a circular list, the tail's next pointer is the head. If you only store the head, adding a node to the end of the list requires you to traverse the entire circle just to find the last element. If you store the tail, you have $O(1)$ access to both the end of the list (the tail) and the start of the list (tail->next). It's a small shift in perspective that saves you a lot of CPU cycles.

typedef struct Node {
    int id;
    struct Node* next;
} Node;

Node* createNode(int id) {
    Node* newNode = malloc(sizeof(Node));
    newNode->id = id;
    newNode->next = NULL;
    return newNode;
}

void insertEnd(Node** tail, int id) {
    Node* newNode = createNode(id);
    if (*tail == NULL) {
        *tail = newNode;
        newNode->next = newNode; // Points to itself to start the circle
    } else {
        Node* head = (*tail)->next;
        newNode->next = head;
        (*tail)->next = newNode;
        *tail = newNode; // Move tail to the new last element
    }
}

Breaking the Infinite Loop

Traversal is where most people trip up. In a linear list, you just loop until you hit NULL. In a circular list, NULL doesn't exist. If you use a standard while(current != NULL) loop, your program will spin forever until it crashes or you kill the process. I've seen this happen in production code more often than I'd like to admit.

To traverse a circular list, you have to remember where you started. You save a pointer to the head, move forward, and stop the moment you see that head pointer again. It's a do-while loop's perfect use case because you want to process the first node before checking if you've completed the circle.

void printList(Node* tail) {
    if (tail == NULL) return;

    Node* head = tail->next;
    Node* current = head;
    do {
        printf("Player %d\n", current->id);
        current = current->next;
    } while (current != head);
}

Managing Node Removal

Deleting a node in a circular list is mostly the same as a linear list, but with one critical edge case: what happens when you delete the last remaining node? If you don't explicitly set your tail pointer to NULL, you'll end up with a dangling pointer that thinks it's still part of a circle. Also, if you delete the head, you must remember to update the tail's next pointer to point to the new head, otherwise, you've broken the circle and turned your list back into a linear one—or worse, created a memory leak.




📋 Practical Task

Build a Multiplayer Turn Manager

Create a program that simulates a turn-based game using a circular linked list. Your program should:

  • Allow the user to add players (by ID) to the game.
  • Implement a function void advanceTurn(Node** current) that moves the "active player" pointer to the next person in the circle.
  • Implement a function void removePlayer(Node** tail, int id) that removes a player from the game by their ID, ensuring the circle remains intact and the tail is updated if the last node is removed.
  • Run a loop where the user can either "Advance Turn", "Add Player", "Remove Player", or "Show Current Player" until they choose to exit.

Make sure you handle the case where the list becomes empty after a removal to prevent segmentation faults.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.