Representation of Linked List in Memory
A linked list is a fundamental data structure in computer science. It primarily allows efficient insertion and deletion operations compared to arrays. Like arrays, it is also used to implement other data structures such as stacks, queues, and deques.
Memory Representation of a Linked List
Unlike arrays, where elements are stored in contiguous memory locations, linked lists allocate memory dynamically for each node. This means that each node can be located anywhere in memory and is connected via pointers.

Let’s see how memory is allocated for a linked list. The images below illustrate this more clearly:

- Heap Memory: The nodes of a linked list are dynamically allocated from the heap. Heap memory is used for objects whose size may not be known at compile time and allows for dynamic memory management.
- Stack Memory: The pointer
headis typically defined within a function (likemain). Local variables, including pointers, are stored in the stack, which has a fixed size and is automatically managed when functions are called and return.
Allocation & Garbage Collection
In linked lists, memory for nodes is dynamically allocated on the heap. Garbage collection ensures that unused nodes are deallocated, preventing memory leaks. Languages with automatic garbage collection, like Python and Java, handle this automatically. In C and C++, manual memory management is required—using malloc for allocation and free for deallocation.
Allocation
-
Dynamic Memory: Linked lists allocate memory for each node dynamically, meaning memory is requested as needed during runtime.
-
Heap Allocation: Nodes are typically allocated on the heap, a region of memory where memory can be dynamically requested and released.
-
malloc(in C/C++): Themallocfunction allocates memory for a node, and the address of the allocated memory is returned.
Garbage Collection
-
Unreachable Nodes: When a node in a linked list becomes unreachable (i.e., no longer referenced by any part of the program), it is considered garbage and is eligible for collection.
-
Automatic vs. Manual:
- Automatic (e.g., Python, Java): The garbage collector automatically identifies and deallocates unreachable memory, freeing it for reuse.
- Manual (e.g., C, C++): The programmer is responsible for explicitly deallocating memory using the
freefunction when it’s no longer needed.
-
Mark-and-Sweep (used in some garbage collectors): This algorithm involves two phases: marking reachable objects and sweeping to reclaim memory occupied by garbage.
-
Copying (another garbage collection approach): The heap is divided into two halves, and live objects are copied to one half, allowing the other half to be reclaimed.
-
Reference Counting: Each object maintains a reference count. When the count drops to zero, the object is considered unreachable and can be deallocated.
Benefits of Garbage Collection
-
Prevents Memory Leaks: Automatically deallocating unreachable memory prevents memory leaks, which occur when memory is allocated but never freed, potentially leading to system instability.
-
Simplifies Memory Management: Automatic garbage collection simplifies programming by relieving developers of the burden of manual memory management.
-
Avoids Dangling Pointers: By deallocating unreachable memory, garbage collection helps avoid dangling pointers—pointers that refer to memory that has already been freed.
-
Avoids Double Free Errors: Garbage collection also prevents double-free errors, where the same memory region is freed more than once.
// Allocation Node* node = (Node*)malloc(sizeof(Node)); node->data = 10; // Deallocation (when node is no longer needed) free(node);
Operations on Linked List
Basic operations that can be performed on linked lists include:
- Traversal
- Removal of a node
- Insertion of a node
- Searching
- Sorting
For simplicity, singly linked lists will be used to explain these operations below.
1. Traversal of a Linked List
Traversing a linked list means going through it by following the links from one node to the next.
Traversal is typically done to search for a specific node, read or modify a node’s contents, remove a node, or insert a node before or after a specific node.
To traverse a singly linked list, start at the head node and follow the next pointers until NULL is reached, as shown in the animation below:

#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
void traverseAndPrint(Node* head) {
Node* currentNode = head;
while (currentNode) {
cout << currentNode->data << " -> ";
currentNode = currentNode->next;
}
cout << "nullptr" << endl;
}
int main() {
Node* node1 = new Node;
Node* node2 = new Node;
Node* node3 = new Node;
Node* node4 = new Node;
Node* node5 = new Node;
node1->data = 7;
node2->data = 11;
node3->data = 3;
node4->data = 2;
node5->data = 9;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = nullptr;
traverseAndPrint(node1);
// Free the allocated memory
delete node1;
delete node2;
delete node3;
delete node4;
delete node5;
return 0;
}Output:
7 -> 11 -> 3 -> 2 -> 9 -> nullptr2. Deletion in a Linked List
- At the beginning: Update the head to point to the next node.
- At a specific position: Update the previous node’s
nextpointer to skip the deleted node. - At the end: Set the previous node’s
nextpointer tonullptr.
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
// Utility to print the list
void traverse(Node* head) {
Node* temp = head;
while (temp) {
cout << temp->data << " -> ";
temp = temp->next;
}
cout << "nullptr" << endl;
}
// Delete from beginning
Node* deleteFromBeginning(Node* head) {
if (!head) return nullptr;
Node* temp = head;
head = head->next;
delete temp;
return head;
}
// Delete at a given position (0-based index)
Node* deleteAtPosition(Node* head, int pos) {
if (!head) return nullptr;
if (pos == 0) return deleteFromBeginning(head);
Node* temp = head;
for (int i = 0; temp && i < pos - 1; ++i)
temp = temp->next;
if (temp && temp->next) {
Node* toDelete = temp->next;
temp->next = toDelete->next;
delete toDelete;
}
return head;
}
// Delete from end
Node* deleteFromEnd(Node* head) {
if (!head || !head->next) {
delete head;
return nullptr;
}
Node* temp = head;
while (temp->next && temp->next->next)
temp = temp->next;
delete temp->next;
temp->next = nullptr;
return head;
}
int main() {
Node* head = new Node{7, new Node{11, new Node{3, new Node{2, new Node{9, nullptr}}}}};
cout << "Original List:\n";
traverse(head);
head = deleteFromBeginning(head);
cout << "\nAfter deleting from beginning:\n";
traverse(head);
head = deleteAtPosition(head, 2);
cout << "\nAfter deleting from position 2:\n";
traverse(head);
head = deleteFromEnd(head);
cout << "\nAfter deleting from end:\n";
traverse(head);
// Free remaining nodes
while (head) {
Node* temp = head;
head = head->next;
delete temp;
}
return 0;
}Output:
Original List:
7 -> 11 -> 3 -> 2 -> 9 -> nullptr
After deleting from beginning:
11 -> 3 -> 2 -> 9 -> nullptr
After deleting from position 2:
11 -> 3 -> 9 -> nullptr
After deleting from end:
11 -> 3 -> nullptr3. Insertion in a Linked List
Insertion involves adding a new node to the list. It can be done in three common ways:
- At the beginning: Make the new node point to the current head and update the head.
- At a given position (middle): Traverse the list up to the position and update pointers to insert the new node.
- At the end: Traverse to the last node and append the new node by setting the last node’s
nextto point to it.
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
// Utility to print the list
void printList(Node* head) {
while (head) {
cout << head->data << " -> ";
head = head->next;
}
cout << "nullptr" << endl;
}
// Insert at beginning
Node* insertAtBeginning(Node* head, int value) {
Node* newNode = new Node{value, head};
return newNode;
}
// Insert at specific position (0-based index)
Node* insertAtPosition(Node* head, int value, int pos) {
if (pos == 0) return insertAtBeginning(head, value);
Node* temp = head;
for (int i = 0; temp && i < pos - 1; ++i)
temp = temp->next;
if (!temp) {
cout << "Position out of bounds.\n";
return head;
}
Node* newNode = new Node{value, temp->next};
temp->next = newNode;
return head;
}
// Insert at end
Node* insertAtEnd(Node* head, int value) {
Node* newNode = new Node{value, nullptr};
if (!head) return newNode;
Node* temp = head;
while (temp->next)
temp = temp->next;
temp->next = newNode;
return head;
}
int main() {
Node* head = nullptr;
head = insertAtBeginning(head, 10);
head = insertAtBeginning(head, 5);
cout << "After inserting 5 and 10 at beginning:\n";
printList(head);
head = insertAtEnd(head, 20);
cout << "\nAfter inserting 20 at end:\n";
printList(head);
head = insertAtPosition(head, 15, 2);
cout << "\nAfter inserting 15 at position 2:\n";
printList(head);
// Free memory
while (head) {
Node* temp = head;
head = head->next;
delete temp;
}
return 0;
}Output:
After inserting 5 and 10 at beginning:
5 -> 10 -> nullptr
After inserting 20 at end:
5 -> 10 -> 20 -> nullptr
After inserting 15 at position 2:
5 -> 10 -> 15 -> 20 -> nullptr4. Searching in a Linked List
This involves traversing the list and comparing each node’s data with the target value until a match is found or the end is reached.
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
bool search(Node* head, int target) {
Node* temp = head;
while (temp) {
if (temp->data == target) return true;
temp = temp->next;
}
return false;
}
int main() {
Node* head = new Node{4, new Node{15, new Node{7, new Node{29, nullptr}}}};
cout << "Searching for 15: " << (search(head, 15) ? "Found" : "Not Found") << endl;
cout << "Searching for 99: " << (search(head, 99) ? "Found" : "Not Found") << endl;
// Free memory
while (head) {
Node* temp = head;
head = head->next;
delete temp;
}
return 0;
}Output:
Searching for 15: Found
Searching for 99: Not Found5. Sorting a Linked List
Linked lists are often sorted using Merge Sort, which is efficient and works well with linked lists due to their nature (no need for random access).
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
// Utility to print the list
void printList(Node* head) {
while (head) {
cout << head->data << " -> ";
head = head->next;
}
cout << "nullptr" << endl;
}
// Merge two sorted lists
Node* merge(Node* l1, Node* l2) {
if (!l1) return l2;
if (!l2) return l1;
if (l1->data < l2->data) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l1, l2->next);
return l2;
}
}
// Find the middle of the list
Node* getMiddle(Node* head) {
if (!head) return head;
Node* slow = head;
Node* fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// Merge sort implementation
Node* mergeSort(Node* head) {
if (!head || !head->next) return head;
Node* middle = getMiddle(head);
Node* nextHalf = middle->next;
middle->next = nullptr;
Node* left = mergeSort(head);
Node* right = mergeSort(nextHalf);
return merge(left, right);
}
int main() {
Node* head = new Node{8, new Node{3, new Node{1, new Node{7, new Node{5, nullptr}}}}};
cout << "Original List:\n";
printList(head);
head = mergeSort(head);
cout << "\nSorted List:\n";
printList(head);
// Free memory
while (head) {
Node* temp = head;
head = head->next;
delete temp;
}
return 0;
}Output:
Original List:
8 -> 3 -> 1 -> 7 -> 5 -> nullptr
Sorted List:
1 -> 3 -> 5 -> 7 -> 8 -> nullptrDoubly Linked Lists
A doubly linked list is a more advanced data structure than a singly linked list, offering several advantages. The primary advantage is that it allows efficient traversal of the list in both directions. This is possible because each node contains a pointer to both the previous node and the next node. As a result, insertion and deletion operations can be performed more efficiently, and the list can be traversed forward and backward with ease.

Representation
In a doubly linked list, each node consists of three fields:
- Data
- Pointer to the next node (
next) - Pointer to the previous node (
prev)

struct Node {
// To store the value or data
int data;
// Pointer to the previous node
Node* prev;
// Pointer to the next node
Node* next;
// Constructor
Node(int d) {
data = d;
prev = next = nullptr;
}
};Each node in a doubly linked list stores the data it holds, a pointer to the next node, and a pointer to the previous node. By connecting these nodes via the next and prev pointers, we can traverse the list in both directions—forward and backward—which is the key characteristic of a doubly linked list.
1. Traversal in a Doubly Linked List
Traversal in a doubly linked list involves visiting each node, processing its data, and moving to the next or previous node using the next and prev pointers.
Step-by-Step Approach
-
Start from the head of the list.
-
Traverse forward:
- Visit the current node and process its data (e.g., print it).
- Move to the next node using
current = current->next. - Repeat until the end of the list (
current == nullptr).
-
Optionally, traverse backward:
- Start from the tail (last node).
- Visit the current node and process its data.
- Move to the previous node using
current = current->prev. - Repeat until the beginning of the list (
current == nullptr).
This traversal is useful for displaying or processing all nodes in a doubly linked list.
// C++ Program for Forward Traversal (Iterative) of a Doubly Linked List
#include <iostream>
using namespace std;
struct Node {
int data;
Node *next;
Node *prev;
Node(int val) {
data = val;
next = nullptr;
prev = nullptr;
}
};
// Function to traverse the doubly linked list in forward direction
void forwardTraversal(Node *head) {
Node *curr = head;
while (curr != nullptr) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 3
Node *head = new Node(1);
Node *second = new Node(2);
Node *third = new Node(3);
head->next = second;
second->prev = head;
second->next = third;
third->prev = second;
cout << "Forward Traversal: ";
forwardTraversal(head);
return 0;
}Output
Forward Traversal: 1 2 3 - Time Complexity: O(n), where
nis the number of nodes in the list. - Auxiliary Space: O(1)
2. Finding the Length of a Doubly Linked List
A doubly linked list (DLL) is a type of linked list where each node has two pointers:
- One pointing to the next node.
- One pointing to the previous node.
To find the length of a doubly linked list, we traverse the list while counting the nodes.
Step-by-Step Approach
-
Initialize a counter: Start with
count = 0. -
Start from the head: Set a pointer (
current) to the head. -
Traverse the list:
- While
currentis notnullptr, incrementcount. - Move to the next node:
current = current->next.
- While
-
End the loop: When
currentbecomesnullptr, stop. -
Return the count.
#include <bits/stdc++.h>
using namespace std;
class Node {
public:
int data;
Node *next;
Node *prev;
Node(int val) {
data = val;
next = nullptr;
prev = nullptr;
}
};
// Function to find the size of the linked list
int findSize(Node *curr) {
int size = 0;
while (curr != nullptr) {
size++;
curr = curr->next;
}
return size;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 3 <-> 4
Node *head = new Node(1);
head->next = new Node(2);
head->next->prev = head;
head->next->next = new Node(3);
head->next->next->prev = head->next;
head->next->next->next = new Node(4);
head->next->next->next->prev = head->next->next;
cout << "Length of the list: " << findSize(head);
return 0;
}Output
Length of the list: 43. Insertion in a Doubly Linked List
Insertion in a Doubly Linked List (DLL) involves adding a new node at a specific position while maintaining the connections between nodes. Since each node contains a pointer to both the previous and the next node, insertion requires adjusting these pointers carefully.
There are three primary types of insertion in a Doubly Linked List:
1. Insertion at the Beginning
- Create a new node with the given data.
- Set the
nextpointer of the new node to the current head. - If the list is not empty, update the
prevpointer of the current head to point to the new node. - Update the head of the list to the new node.
// C++ program to insert a node at the beginning of a doubly linked list
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
Node *next, *prev;
Node(int new_data) {
data = new_data;
next = prev = NULL;
}
};
// Function to insert a new node at the front of the doubly linked list
Node* insertAtFront(Node* head, int new_data) {
Node* new_node = new Node(new_data);
new_node->next = head;
if (head != NULL)
head->prev = new_node;
return new_node;
}
void printList(Node* head) {
Node* curr = head;
while (curr != NULL) {
cout << " " << curr->data;
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 2 <-> 3 <-> 4
Node* head = new Node(2);
head->next = new Node(3);
head->next->prev = head;
head->next->next = new Node(4);
head->next->next->prev = head->next;
cout << "Original Linked List:";
printList(head);
cout << "After inserting node at the front:";
int data = 1;
head = insertAtFront(head, data);
printList(head);
return 0;
}Output
Original Linked List: 2 3 4
After inserting node at the front: 1 2 3 4- Time Complexity: O(1)
- Auxiliary Space: O(1)
2. Insertion at the End
- Create a new node with the given data.
- If the list is empty, set the new node as the head.
- Traverse the list to find the last node.
- Set the
nextpointer of the last node to the new node. - Set the
prevpointer of the new node to the last node.
// C++ program to insert a node at the end of a doubly linked list
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
Node *next, *prev;
Node(int new_data) {
data = new_data;
next = prev = nullptr;
}
};
Node* insertEnd(Node* head, int new_data) {
Node* new_node = new Node(new_data);
if (head == NULL) {
return new_node;
}
Node* curr = head;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = new_node;
new_node->prev = curr;
return head;
}
void printList(Node* head) {
Node* curr = head;
while (curr != NULL) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 3
Node* head = new Node(1);
head->next = new Node(2);
head->next->prev = head;
head->next->next = new Node(3);
head->next->next->prev = head->next;
cout << "Original Linked List: ";
printList(head);
cout << "Inserting node with data 4 at the end: ";
int data = 4;
head = insertEnd(head, data);
printList(head);
return 0;
}Output
Original Linked List: 1 2 3
Inserting node with data 4 at the end: 1 2 3 4 - Time Complexity: O(N), where N is the number of nodes
- Auxiliary Space: O(1)
3. Insertion at a Specific Position
- Create a new node with the given data.
- If inserting at the beginning, follow the steps for insertion at the start.
- Traverse the list to find the node after which the new node should be inserted.
- Set the
nextpointer of the new node to thenextof the current node. - Set the
prevpointer of the new node to the current node. - Update the
prevpointer of the next node to point to the new node (if it exists). - Update the
nextpointer of the current node to point to the new node.
// C++ program to insert a node at a given position
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
Node *next, *prev;
Node(int new_data) {
data = new_data;
next = prev = nullptr;
}
};
Node* insertAtPosition(Node* head, int pos, int new_data) {
Node* new_node = new Node(new_data);
// Insertion at the beginning
if (pos == 1) {
new_node->next = head;
if (head != NULL)
head->prev = new_node;
return new_node;
}
Node* curr = head;
for (int i = 1; i < pos - 1 && curr != NULL; ++i) {
curr = curr->next;
}
if (curr == NULL) {
cout << "Position is out of bounds." << endl;
delete new_node;
return head;
}
new_node->prev = curr;
new_node->next = curr->next;
if (curr->next != NULL)
curr->next->prev = new_node;
curr->next = new_node;
return head;
}
void printList(Node* head) {
Node* curr = head;
while (curr != NULL) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 4
Node* head = new Node(1);
head->next = new Node(2);
head->next->prev = head;
head->next->next = new Node(4);
head->next->next->prev = head->next;
cout << "Original Linked List: ";
printList(head);
cout << "Inserting node with data 3 at position 3: ";
int data = 3;
int pos = 3;
head = insertAtPosition(head, pos, data);
printList(head);
return 0;
}Output
Original Linked List: 1 2 4
Inserting node with data 3 at position 3: 1 2 3 4 - Time Complexity: O(N)
- Auxiliary Space: O(1)
4. Deletion in a Doubly Linked List
Deletion in a Doubly Linked List (DLL) involves removing a node while maintaining the integrity of the list. Since each node contains pointers to both its previous and next nodes, deletion requires careful pointer adjustments to ensure no broken links occur.
1. Deletion at the Beginning
- Check if the list is empty. If it is, return as there is nothing to delete.
- Store the current head node in a temporary variable.
- Move the head pointer to the next node.
- If the new head exists, update its
prevpointer toNULL. - Delete the old head node to free memory.
// C++ program to delete the head node
// from a singly linked list
#include <iostream>
using namespace std;
struct Node {
int data;
Node *next;
Node(int x) {
data = x;
next = nullptr;
}
};
// Delete the head node and return the new head
Node* deleteHead(Node* head) {
// Check if the list is empty
if (head == nullptr)
return nullptr;
// Store the current head in a temporary variable
Node* temp = head;
// Move the head pointer to the next node
head = head->next;
// Free the memory of the old head node
delete temp;
return head;
}
void printList(Node* curr) {
while (curr != nullptr) {
cout << curr->data << " ";
curr = curr->next;
}
}
int main() {
// Create a hardcoded linked list:
// 3 -> 12 -> 15 -> 18
Node* head = new Node(3);
head->next = new Node(12);
head->next->next = new Node(15);
head->next->next->next = new Node(18);
head = deleteHead(head);
printList(head);
return 0;
}Output
12 15 18- Time Complexity: O(1) — The deletion is performed in constant time.
- Space Complexity: O(1)
2. Deletion at the End
- Check if the list is empty. If it is, return.
- Traverse the list to find the last node.
- Store the last node in a temporary variable.
- Update the
nextpointer of the second-last node toNULL, making it the new tail. - Delete the last node to free memory.
// C++ Program to delete a node from the end of a Doubly Linked List
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
Node *prev, *next;
Node(int d) {
data = d;
prev = next = NULL;
}
};
// Function to delete the last node of the doubly linked list
Node* delLast(Node* head) {
// Corner cases
if (head == NULL)
return NULL;
if (head->next == NULL) {
delete head;
return NULL;
}
// Traverse to the last node
Node* curr = head;
while (curr->next != NULL)
curr = curr->next;
// Update the previous node's next pointer
curr->prev->next = NULL;
// Delete the last node
delete curr;
return head;
}
void printList(Node* head) {
Node* curr = head;
while (curr != NULL) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 3
Node* head = new Node(1);
head->next = new Node(2);
head->next->prev = head;
head->next->next = new Node(3);
head->next->next->prev = head->next;
cout << "Original Linked List: ";
printList(head);
head = delLast(head);
cout << "After Deletion at the end: ";
printList(head);
return 0;
}Output
Original Linked List: 1 2 3
After Deletion at the end: 1 2 - Time Complexity: O(N), where N is the number of nodes in the list
- Space Complexity: O(1)
3. Deletion at a Specific Position
- Check if the list is empty. If it is, return.
- Traverse the list to find the node to be deleted.
- Store the node to be deleted in a temporary variable.
- Update the
nextpointer of the previous node to point to the next node. - Update the
prevpointer of the next node to point to the previous node (if it exists). - Delete the target node to free memory.
// C++ Program to delete a node at a specific position
// in a Doubly Linked List
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node *prev, *next;
Node(int d) {
data = d;
prev = next = NULL;
}
};
// Function to delete a node at a specific position
Node* delPos(Node* head, int pos) {
if (head == NULL)
return head;
Node* curr = head;
// Traverse to the node at the given position
for (int i = 1; curr != NULL && i < pos; ++i)
curr = curr->next;
// If position is out of range
if (curr == NULL)
return head;
// Update pointers
if (curr->prev != NULL)
curr->prev->next = curr->next;
if (curr->next != NULL)
curr->next->prev = curr->prev;
// If deleting the head node
if (head == curr)
head = curr->next;
delete curr;
return head;
}
void printList(Node* head) {
Node* curr = head;
while (curr != NULL) {
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
int main() {
// Create a hardcoded doubly linked list:
// 1 <-> 2 <-> 3
Node* head = new Node(1);
head->next = new Node(2);
head->next->prev = head;
head->next->next = new Node(3);
head->next->next->prev = head->next;
head = delPos(head, 2);
printList(head);
return 0;
}Output
1 3- Time Complexity: O(N), where N is the number of nodes
- Space Complexity: O(1)
Advantages of Doubly Linked List
- Efficient traversal in both directions: Allows movement forward and backward.
- Easy insertion and deletion: Modifying a node is simpler due to the presence of both
prevandnextpointers. - Can be used to implement stacks and queues: Supports operations needed for these data structures.
Disadvantages of Doubly Linked List
- More complex than singly linked lists: Requires maintaining two pointers per node.
- More memory overhead: Additional space is needed to store the extra pointer.
Applications of Doubly Linked List
- Undo/redo operations in text editors
- Browser history management to navigate back and forth between visited pages
- Playlist management in music players
- Cache implementation (e.g., LRU Cache)
- Implementation of Deque (double-ended queue)
Circular Linked List
A circular linked list is a data structure where the last node connects back to the first, forming a loop. This structure allows for continuous traversal without any interruptions. Circular linked lists are especially helpful for tasks like scheduling and managing playlists, allowing for smooth navigation. In this tutorial, we’ll cover the basics of circular linked lists, how to work with them, their advantages and disadvantages, and their applications.
A circular linked list is a special type of linked list where all the nodes are connected to form a circle. Unlike a regular linked list, which ends with a node pointing to NULL, the last node in a circular linked list points back to the first node. This means that you can keep traversing the list without ever reaching a NULL value.
Types of Circular Linked Lists
We can create a circular linked list from both singly linked lists and doubly linked lists. So, circular linked lists are basically of two types:
1. Circular Singly Linked List
In a circular singly linked list, each node has just one pointer called the “next” pointer. The next pointer of the last node points back to the first node, forming a circle. In this type of linked list, we can only move through the list in one direction.

2. Circular Doubly Linked List
In a circular doubly linked list, each node has two pointers: prev and next, similar to a doubly linked list. The prev pointer points to the previous node and the next pointer points to the next node. Here, in addition to the last node storing the address of the first node, the first node also stores the address of the last node.

Operations on the Circular Linked List
1. Insertion in the Circular Linked List
Insertion is a fundamental operation in linked lists that involves adding a new node to the list. The only extra step is connecting the last node to the first one. In the circular linked list mentioned below, we can insert nodes in four ways:
-
Insertion in an Empty Circular Linked List - To insert a node in an empty circular linked list, we create a new node with the given data, set its next pointer to point to itself, and update the
lastpointer to reference this new node.
#include <iostream> using namespace std; struct Node{ int data; Node *next; Node(int value){ data = value; next = nullptr; } }; // Function to insert a node into an empty circular singly linked list Node *insertInEmptyList(Node *last, int data){ if (last != nullptr) return last; // Create a new node Node *newNode = new Node(data); // Point newNode to itself newNode->next = newNode; // Update last to point to the new node last = newNode; return last; } void printList(Node* last){ if (last == NULL) return; // Start from the head node Node* head = last->next; while (true) { cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main(){ Node *last = nullptr; // Insert a node into the empty list last = insertInEmptyList(last, 1); // Print the list cout << "List after insertion: "; printList(last); return 0; }Output
List after insertion: 1- Time Complexity: O(1)
- Auxiliary Space: O(1)
-
Insertion at the Beginning in Circular Linked List - To insert a new node at the beginning of a circular linked list, we create a new node and check if the list is empty. If it is, the new node points to itself. If not, we make the new node’s next pointer point to the current head (
last->next) and update the last node’s next to the new node, preserving the circular structure.
#include <iostream> using namespace std; struct Node { int data; Node* next; Node(int value){ data = value; next = nullptr; } }; // Function to insert a node at the beginning of the circular linked list Node* insertAtBeginning(Node* last, int value){ Node* newNode = new Node(value); if (last == nullptr) { newNode->next = newNode; return newNode; } newNode->next = last->next; last->next = newNode; return last; } void printList(Node* last){ if (last == NULL) return; Node* head = last->next; while (true) { cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main(){ Node* first = new Node(2); first->next = new Node(3); first->next->next = new Node(4); Node* last = first->next->next; last->next = first; cout << "Original list: "; printList(last); last = insertAtBeginning(last, 5); cout << "List after inserting 5 at the beginning: "; printList(last); return 0; }Output
Original list: 2 3 4 List after inserting 5 at the beginning: 5 2 3 4- Time Complexity: O(1)
- Auxiliary Space: O(1)
-
Insertion at the End in Circular Linked List - To insert a node at the end of a circular linked list, we create a new node. If the list is empty, we make it point to itself. Otherwise, we update the tail’s next pointer to the new node and then set the tail to the new node, preserving the circular linkage.

#include <iostream> using namespace std; struct Node{ int data; Node *next; Node(int value){ data = value; next = nullptr; } }; // Function to insert a node at the end of a circular linked list Node *insertEnd(Node *tail, int value){ Node *newNode = new Node(value); if (tail == nullptr){ tail = newNode; newNode->next = newNode; } else{ newNode->next = tail->next; tail->next = newNode; tail = newNode; } return tail; } void printList(Node *last){ if (last == NULL) return; Node *head = last->next; while (true){ cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main(){ Node *first = new Node(2); first->next = new Node(3); first->next->next = new Node(4); Node *last = first->next->next; last->next = first; cout << "Original list: "; printList(last); last = insertEnd(last, 5); last = insertEnd(last, 6); cout << "List after inserting 5 and 6: "; printList(last); return 0; }Output
Original list: 2 3 4 List after inserting 5 and 6: 2 3 4 5 6- Time Complexity: O(1)
- Auxiliary Space: O(1)
4. Insertion at a Specific Position in Circular Linked List - To insert a node at a specific position in a circular linked list, we handle edge cases for an empty list and invalid positions. For valid positions, we traverse the list and adjust the pointers to insert the new node, updating the tail if it’s inserted at the end.

#include <iostream>
using namespace std;
struct Node{
int data;
Node *next;
Node(int value){
data = value;
next = nullptr;
}
};
// Function to insert a node at a specific position in a circular linked list
Node *insertAtPosition(Node *last, int data, int pos){
if (last == nullptr){
if (pos != 1){
cout << "Invalid position!" << endl;
return last;
}
Node *newNode = new Node(data);
last = newNode;
last->next = last;
return last;
}
Node *newNode = new Node(data);
Node *curr = last->next;
if (pos == 1){
newNode->next = curr;
last->next = newNode;
return last;
}
for (int i = 1; i < pos - 1; ++i){
curr = curr->next;
if (curr == last->next){
cout << "Invalid position!" << endl;
return last;
}
}
newNode->next = curr->next;
curr->next = newNode;
if (curr == last) last = newNode;
return last;
}
void printList(Node *last){
if (last == NULL) return;
Node *head = last->next;
while (true){
cout << head->data << " ";
head = head->next;
if (head == last->next) break;
}
cout << endl;
}
int main(){
Node *first = new Node(2);
first->next = new Node(3);
first->next->next = new Node(4);
Node *last = first->next->next;
last->next = first;
cout << "Original list: ";
printList(last);
int data = 5, pos = 2;
last = insertAtPosition(last, data, pos);
cout << "List after insertions: ";
printList(last);
return 0;
}Output
Original list: 2 3 4
List after insertions: 2 5 3 4 Deletion from a Circular Linked List
Deletion involves removing a node from the linked list. The main difference is that we need to ensure the list remains circular after the deletion. We can delete a node in a circular linked list in three ways:
-
Delete the first node in a circular linked list
To delete the first node of a circular linked list, we check if the list is empty or has only one node. If so, we handle those cases by deleting the node and updating the last pointer. For multiple nodes, we update the last node’s next pointer to skip the head and free the head node, returning the updated last pointer.

#include <iostream> using namespace std; struct Node { int data; Node* next; Node(int value) { data = value; next = nullptr; } }; // Function to delete the first node of the circular linked list Node* deleteFirstNode(Node* last) { if (last == nullptr) { // If the list is empty cout << "List is empty" << endl; return nullptr; } Node* head = last->next; if (head == last) { // If there is only one node in the list delete head; last = nullptr; } else { // More than one node in the list last->next = head->next; delete head; } return last; } void printList(Node* last) { if(last == NULL) return ; Node *head = last->next; while (true){ cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main() { // Create circular linked list: 2, 3, 4 Node* first = new Node(2); first->next = new Node(3); first->next->next = new Node(4); Node* last = first->next->next; last->next = first; cout << "Original list: "; printList(last); // Delete the first node last = deleteFirstNode(last); cout << "List after deleting first node: "; printList(last); return 0; }Output
Original list: 2 3 4 List after deleting first node: 3 4 -
Delete a specific node in a circular linked list
To delete a specific node from a circular linked list, we handle empty list and single-node cases. For other nodes, we use two pointers to find the node, update the previous node’s next pointer to skip the target, and delete it, updating the last pointer if needed.

#include <iostream> using namespace std; struct Node { int data; Node* next; Node(int value) { data = value; next = nullptr; } }; // Function to delete a specific node in the circular linked list Node* deleteSpecificNode(Node* last, int key) { if (last == nullptr) { // If the list is empty cout << "List is empty, nothing to delete." << endl; return nullptr; } Node* curr = last->next; Node* prev = last; // If the node to be deleted is the only node in the list if (curr == last && curr->data == key) { delete curr; last = nullptr; return last; } // If the node to be deleted is the first node if (curr->data == key) { last->next = curr->next; delete curr; return last; } // Traverse the list to find the node to be deleted while (curr != last && curr->data != key) { prev = curr; curr = curr->next; } // If the node to be deleted is found if (curr->data == key) { prev->next = curr->next; if (curr == last) { last = prev; } delete curr; } else { // If the node to be deleted is not found cout << "Node with data " << key << " not found." << endl; } return last; } // Function to print the circular linked list void printList(Node* last) { if (last == NULL){ cout << "List is Empty"; return; } Node *head = last->next; while (true){ cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main() { // Create circular linked list: 2, 3, 4 Node* first = new Node(2); first->next = new Node(3); first->next->next = new Node(4); Node* last = first->next->next; last->next = first; cout << "Original list: "; printList(last); // Delete a specific node int key = 3; last = deleteSpecificNode(last, key); cout << "List after deleting node " << key << ": "; printList(last); return 0; }Output
Original list: 2 3 4 List after deleting node 3: 2 4 -
Delete the last node in a circular linked list
To delete the last node in a circular linked list, we handle the empty and single-node cases. For multiple nodes, we traverse to find the second-last node, update its next pointer to the head, delete the last node, and return the updated last pointer.

#include <iostream> using namespace std; struct Node { int data; Node* next; Node(int value) { data = value; next = nullptr; } }; // Function to delete the last node in the circular linked list Node* deleteLastNode(Node* last) { if (last == nullptr) { // If the list is empty cout << "List is empty, nothing to delete." << endl; return nullptr; } Node* head = last->next; // If there is only one node in the list if (head == last) { delete last; last = nullptr; return last; } // Traverse the list to find the second last node Node* curr = head; while (curr->next != last) { curr = curr->next; } // Update the second last node's next pointer // to point to head curr->next = head; delete last; last = curr; return last; } void printList(Node* last) { if(last == NULL) return; Node *head = last->next; while (true){ cout << head->data << " "; head = head->next; if (head == last->next) break; } cout << endl; } int main() { // Create circular linked list: 2, 3, 4 Node* first = new Node(2); first->next = new Node(3); first->next->next = new Node(4); Node* last = first->next->next; last->next = first; cout << "Original list: "; printList(last); // Delete the last node last = deleteLastNode(last); cout << "List after deleting last node: "; printList(last); return 0; }Output
Original list: 2 3 4 List after deleting last node: 2 3
Searching in a Circular Linked List
Searching in a circular linked list is similar to searching in a regular linked list. We start at a given node and traverse the list until we either find the target value or return to the starting node. Since the list is circular, we must keep track of where we started to avoid an infinite loop.
#include <iostream>
using namespace std;
class Search {
private:
class Node {
public:
int data;
Node* next;
Node(int data)
{
this->data = data;
next = nullptr;
}
};
public:
Node* head = nullptr;
Node* tempo = nullptr;
// Function to add new nodes at the end of the list
void addNode(int data)
{
Node* new1 = new Node(data);
// If linked list is empty
if (head == nullptr) {
head = new1;
}
else {
tempo->next = new1;
}
tempo = new1;
// last node points to the first node
tempo->next = head;
}
void find(int key)
{
// temp will traverse the circular linked list for
// searching the element
Node* temp = head;
// flag used to check if the element is found or not
int f = 0;
if (head == nullptr) {
cout << "List is empty" << endl;
}
else {
do {
if (temp->data == key) {
cout << key << " Found" << endl;
f = 1;
break;
}
temp = temp->next;
} while (temp != head);
if (f == 0) {
cout << key << " Not Found" << endl;
}
}
}
};
// Driver code
int main()
{
Search s;
// Adds data to the list
s.addNode(5);
s.addNode(4);
s.addNode(3);
s.addNode(2);
// Search for node 2 in the list
int key = 2;
s.find(key);
// Search for node 6 in the list
key = 6;
s.find(key);
return 0;Output
2 Found
6 Not FoundAdvantages of Circular Linked Lists
- In a circular linked list, the last node points to the first node. There are no null references, making traversal easier and reducing the chances of encountering null pointer exceptions.
- We can traverse the list from any node and return to it without needing to restart from the head, which is useful in applications requiring circular iteration.
- Circular linked lists can easily implement circular queues, where the last element connects back to the first, allowing for efficient resource management.
- In a circular linked list, each node has a reference to the next node in the sequence. Although it doesn’t have a direct reference to the previous node like a doubly linked list, we can still find the previous node by traversing the list.
Disadvantages of Circular Linked Lists
- Circular linked lists are more complex to implement than singly linked lists.
- Traversing a circular linked list without a clear stopping condition can lead to infinite loops if not handled carefully.
- Debugging can be more challenging due to the circular nature, as traditional methods of traversing linked lists may not apply.
Applications of Circular Linked Lists
- They are used for time-sharing among different users, typically through a Round-Robin scheduling mechanism.
- In multiplayer games, a circular linked list can be used to switch between players. After the last player’s turn, the list cycles back to the first player.
- Circular linked lists are often used in buffering applications, such as streaming data, where data is continuously produced and consumed.
- In media players, circular linked lists can manage playlists, allowing users to loop through songs continuously.
- Browsers use circular linked lists to manage the cache. This allows you to navigate back through your browsing history efficiently by pressing the BACK button.
Linked List with Header Node
A linked list with a header node is a type of linked list that contains a special, often empty, node at the beginning of the list. This header node does not store actual data but serves as a reference point for the list. It simplifies certain operations and provides a consistent starting point, even when the list is empty. The header node's next pointer points to the first actual data node in the list, or NULL if the list is empty.
#include <iostream>
#include <cstdlib>
using namespace std;
// Define the structure for a node
struct Node {
int data;
Node* next;
};
// Function to create a new node
Node* createNode(int data) {
Node* newNode = new Node;
if (newNode == nullptr) {
cerr << "Failed to allocate memory for new node" << endl;
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = nullptr;
return newNode;
}
// Function to create a linked list with a header node
Node* createLinkedListWithHeader() {
Node* header = createNode(-1); // Dummy data for header
return header;
}
// Function to insert a new node at the beginning of the list
void insertAtBeginning(Node* header, int data) {
Node* newNode = createNode(data);
newNode->next = header->next;
header->next = newNode;
}
// Function to print the linked list
void printList(Node* header) {
Node* current = header->next;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}
// Function to free the memory allocated for the linked list
void freeList(Node* header) {
Node* current = header;
Node* next;
while (current != nullptr) {
next = current->next;
delete current;
current = next;
}
}
int main() {
// Create a linked list with a header node
Node* myList = createLinkedListWithHeader();
// Insert elements into the list
insertAtBeginning(myList, 3);
insertAtBeginning(myList, 2);
insertAtBeginning(myList, 1);
// Print the list
cout << "Linked List: ";
printList(myList);
// Free the memory used by the list
freeList(myList);
return 0;
}Output
Linked List: 1 2 3 Applications
- Linked lists can be used to implement stacks, queues, deques, sparse matrices, and the adjacency list representation of graphs.
- Dynamic memory allocation in operating systems and compilers (linked list of free blocks).
- Manipulation of polynomials.
- Arithmetic operations on long integers.
- In operating systems, they are used in memory management, process scheduling (e.g., circular linked list for round-robin scheduling), and file systems.
- Algorithms that frequently insert or delete items from large collections of data.
- LRU cache, which uses a doubly linked list to keep track of the most recently used items in a cache.
Applications of Linked Lists in the Real World:
- The list of songs in a music player is linked to the previous and next songs.
- In a web browser, previous and next web page URLs can be linked through the previous and next buttons (doubly linked list).
- In an image viewer, the previous and next images can be linked with the help of the previous and next buttons (doubly linked list).
- Circular linked lists can be used to implement structures that cycle through elements repeatedly.
- Linked lists are preferred over arrays for implementing queue and deque data structures due to fast insertions and deletions from the front.