Module 6 : Linked List
By the end of this module, students will be able to:
- Understand the concept and structure of linked lists
- Differentiate between arrays and linked lists
- Implement singly linked lists in C
- Perform basic operations: insertion, deletion, traversal, and searching
- Understand doubly linked lists and circular linked lists
- Apply linked lists to solve real-world problems
- Debug common linked list errors
- Analyze time and space complexity of linked list operations

1. Introduction to Linked Lists
1.1 What is a Linked List? 
 A linked list is a linear data structure where elements are not stored in contiguous memory locations. Instead, each element (called a node ) contains: 
 
 Data: The actual value stored 
 Pointer(s): Reference to the next (and possibly previous) node 
 
 Analogy: 
Think of a linked list like a treasure hunt: 
 
 Each clue (node) contains information (data) 
 Each clue also tells you where to find the next clue (pointer) 
 You start at the first clue (head) 
 The last clue says "End of hunt" (NULL pointer) 
 
 Visual Representation: 
 Array (Contiguous Memory):
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
 [0] [1] [2] [3] [4]

Linked List (Non-contiguous Memory):
HEAD
 ↓
┌────┬────┐ ┌────┬────┐ ┌────┬────┐ ┌────┬────┐
│ 10 │ ●──┼───→│ 20 │ ●──┼───→│ 30 │ ●──┼───→│ 40 │NULL│
└────┴────┘ └────┴────┘ └────┴────┘ └────┴────┘
 data next data next data next data next
 
 1.2 Why Use Linked Lists? 
 Advantages: 
 
 Dynamic Size: Can grow or shrink at runtime 
 Easy Insertion/Deletion: No need to shift elements 
 Memory Efficient: Allocate memory only when needed 
 Flexible Structure: Can implement stacks, queues, graphs 
 
 Disadvantages: 
 
 No Random Access: Must traverse from beginning 
 Extra Memory: Requires space for pointers 
 Sequential Access: Slower than arrays for direct access 
 Cache Performance: Poor cache locality 
 
 1.3 Types of Linked Lists 
 1. Singly Linked List
 HEAD → [data|next] → [data|next] → [data|NULL]

2. Doubly Linked List
 HEAD ⇄ [prev|data|next] ⇄ [prev|data|next] ⇄ [prev|data|NULL]

3. Circular Linked List
 HEAD → [data|next] → [data|next] → [data|next] ──┐
 ↑ │
 └─────────────────────────────────────────────────┘

4. Circular Doubly Linked List
 HEAD ⇄ [prev|data|next] ⇄ [prev|data|next] ──┐
 ↑ │
 └────────────────────────────────────────────┘
 
 1.4 Linked List vs Array 
 
 
 
 Feature 
 Array 
 Linked List 
 
 
 
 
 Memory Allocation 
 Contiguous 
 Non-contiguous 
 
 
 Size 
 Fixed 
 Dynamic 
 
 
 Access Time 
 O(1) 
 O(n) 
 
 
 Insertion (beginning) 
 O(n) 
 O(1) 
 
 
 Insertion (end) 
 O(1) 
 O(n) or O(1) with tail 
 
 
 Deletion (beginning) 
 O(n) 
 O(1) 
 
 
 Memory Usage 
 Less (no pointers) 
 More (pointers) 
 
 
 Cache Performance 
 Better 
 Worse 
 
 
 Random Access 
 Yes 
 No

2. Node Structure
2.1 Defining a Node 
 In C, a node is typically defined using a structure : 
 // Definition of a node
struct Node {
 int data; // Data part
 struct Node *next; // Pointer to next node
};
 
 Memory Layout: 
 Single Node in Memory:
┌──────────┬──────────┐
│ data │ next │
│ (int) │ (pointer)│
└──────────┴──────────┘
 4 bytes 8 bytes (on 64-bit system)
 
 2.2 Creating a Node 
 Method 1: Static Allocation (Limited) 
 struct Node node1;
node1.data = 10;
node1.next = NULL;
 
 Method 2: Dynamic Allocation (Recommended) 
 #include <stdio.h>
#include <stdlib.h>

struct Node {
 int data;
 struct Node *next;
};

int main() {
 // Allocate memory for new node
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 
 // Check if allocation successful
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return 1;
 }
 
 // Initialize node
 newNode->data = 10;
 newNode->next = NULL;
 
 printf("Node created with data: %d\n", newNode->data);
 
 // Free memory when done
 free(newNode);
 
 return 0;
}
 
 2.3 The Arrow Operator (->) 
 When working with pointers to structures, use the arrow operator: 
 struct Node *ptr;

// These are equivalent:
ptr->data = 10; // Arrow operator (preferred)
(*ptr).data = 10; // Dereference then dot operator
 
 Why use ->? 
 
 More readable 
 Less typing 
 Standard convention in C

3. Singly Linked List Operations
3.1 Creating an Empty List 
 #include <stdio.h>
#include <stdlib.h>

struct Node {
 int data;
 struct Node *next;
};

// Initialize head pointer
struct Node *head = NULL;
 
 3.2 Insertion Operations 
 3.2.1 Insert at Beginning 
 void insertAtBeginning(struct Node **head, int value) {
 // Create new node
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 // Set data and next pointer
 newNode->data = value;
 newNode->next = *head;
 
 // Update head
 *head = newNode;
}

// Usage
int main() {
 struct Node *head = NULL;
 
 insertAtBeginning(&head, 30);
 insertAtBeginning(&head, 20);
 insertAtBeginning(&head, 10);
 
 // List now: 10 → 20 → 30 → NULL
 
 return 0;
}
 
 Visual Steps: 
 Initial: head → NULL

Step 1: Create node with data = 10
newNode → [10|NULL]

Step 2: Point newNode->next to current head
newNode → [10|●] → NULL

Step 3: Update head to newNode
head → [10|NULL]

Step 4: Insert 20
head → [20|●] → [10|NULL]

Step 5: Insert 30
head → [30|●] → [20|●] → [10|NULL]
 
 Time Complexity: O(1)
 Space Complexity: O(1) 
 3.2.2 Insert at End 
 void insertAtEnd(struct Node **head, int value) {
 // Create new node
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 newNode->data = value;
 newNode->next = NULL;
 
 // If list is empty
 if (*head == NULL) {
 *head = newNode;
 return;
 }
 
 // Traverse to last node
 struct Node *temp = *head;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 
 // Insert at end
 temp->next = newNode;
}
 
 Visual Steps: 
 List: head → [10|●] → [20|●] → [30|NULL]

Step 1: Create newNode with data = 40
newNode → [40|NULL]

Step 2: Traverse to last node
temp moves: [10] → [20] → [30]

Step 3: Connect last node to newNode
head → [10|●] → [20|●] → [30|●] → [40|NULL]
 
 Time Complexity: O(n) - must traverse entire list
 Space Complexity: O(1) 
 3.2.3 Insert at Position 
 void insertAtPosition(struct Node **head, int value, int position) {
 // Create new node
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 newNode->data = value;
 
 // Insert at beginning (position 0)
 if (position == 0) {
 newNode->next = *head;
 *head = newNode;
 return;
 }
 
 // Traverse to position-1
 struct Node *temp = *head;
 for (int i = 0; i < position - 1 && temp != NULL; i++) {
 temp = temp->next;
 }
 
 // Check if position is valid
 if (temp == NULL) {
 printf("Invalid position!\n");
 free(newNode);
 return;
 }
 
 // Insert node
 newNode->next = temp->next;
 temp->next = newNode;
}
 
 Visual Steps (Insert 25 at position 2): 
 Initial: head → [10|●] → [20|●] → [30|NULL]
 0 1 2

Step 1: Create newNode [25|NULL]

Step 2: Traverse to position 1 (position - 1)
temp → [20|●]

Step 3: Connect newNode
newNode->next = temp->next (points to 30)
[25|●] → [30|NULL]

Step 4: Connect temp to newNode
temp->next = newNode
head → [10|●] → [20|●] → [25|●] → [30|NULL]
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.3 Deletion Operations 
 3.3.1 Delete from Beginning 
 void deleteFromBeginning(struct Node **head) {
 // Check if list is empty
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 // Store current head
 struct Node *temp = *head;
 
 // Move head to next node
 *head = (*head)->next;
 
 // Free old head
 free(temp);
}
 
 Visual Steps: 
 Initial: head → [10|●] → [20|●] → [30|NULL]

Step 1: temp = head
temp → [10|●] → [20|●] → [30|NULL]
head → [10|●] → [20|●] → [30|NULL]

Step 2: head = head->next
head → [20|●] → [30|NULL]
temp → [10|●] (to be freed)

Step 3: free(temp)
head → [20|●] → [30|NULL]
 
 Time Complexity: O(1)
 Space Complexity: O(1) 
 3.3.2 Delete from End 
 void deleteFromEnd(struct Node **head) {
 // Check if list is empty
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 // If only one node
 if ((*head)->next == NULL) {
 free(*head);
 *head = NULL;
 return;
 }
 
 // Traverse to second-last node
 struct Node *temp = *head;
 while (temp->next->next != NULL) {
 temp = temp->next;
 }
 
 // Delete last node
 free(temp->next);
 temp->next = NULL;
}
 
 Visual Steps: 
 Initial: head → [10|●] → [20|●] → [30|NULL]

Step 1: Traverse to second-last node
temp → [20|●] → [30|NULL]

Step 2: Free last node
free(temp->next)

Step 3: Set second-last to NULL
head → [10|●] → [20|NULL]
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.3.3 Delete at Position 
 void deleteAtPosition(struct Node **head, int position) {
 // Check if list is empty
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 // Delete first node
 if (position == 0) {
 struct Node *temp = *head;
 *head = (*head)->next;
 free(temp);
 return;
 }
 
 // Traverse to position-1
 struct Node *temp = *head;
 for (int i = 0; i < position - 1 && temp != NULL; i++) {
 temp = temp->next;
 }
 
 // Check if position is valid
 if (temp == NULL || temp->next == NULL) {
 printf("Invalid position!\n");
 return;
 }
 
 // Delete node
 struct Node *nodeToDelete = temp->next;
 temp->next = nodeToDelete->next;
 free(nodeToDelete);
}
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.3.4 Delete by Value 
 void deleteByValue(struct Node **head, int value) {
 // Check if list is empty
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 // If head node contains the value
 if ((*head)->data == value) {
 struct Node *temp = *head;
 *head = (*head)->next;
 free(temp);
 return;
 }
 
 // Search for the node
 struct Node *temp = *head;
 while (temp->next != NULL && temp->next->data != value) {
 temp = temp->next;
 }
 
 // If value not found
 if (temp->next == NULL) {
 printf("Value %d not found!\n", value);
 return;
 }
 
 // Delete node
 struct Node *nodeToDelete = temp->next;
 temp->next = nodeToDelete->next;
 free(nodeToDelete);
}
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.4 Traversal and Display 
 void displayList(struct Node *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 struct Node *temp = head;
 printf("List: ");
 
 while (temp != NULL) {
 printf("%d", temp->data);
 if (temp->next != NULL) {
 printf(" → ");
 }
 temp = temp->next;
 }
 printf(" → NULL\n");
}

// Alternative: Using for loop
void displayList2(struct Node *head) {
 printf("List: ");
 for (struct Node *temp = head; temp != NULL; temp = temp->next) {
 printf("%d → ", temp->data);
 }
 printf("NULL\n");
}
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.5 Searching 
 int search(struct Node *head, int value) {
 struct Node *temp = head;
 int position = 0;
 
 while (temp != NULL) {
 if (temp->data == value) {
 return position; // Found at position
 }
 temp = temp->next;
 position++;
 }
 
 return -1; // Not found
}

// Usage
int main() {
 struct Node *head = NULL;
 
 insertAtEnd(&head, 10);
 insertAtEnd(&head, 20);
 insertAtEnd(&head, 30);
 
 int pos = search(head, 20);
 if (pos != -1) {
 printf("Found at position: %d\n", pos);
 } else {
 printf("Not found!\n");
 }
 
 return 0;
}
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 3.6 Length of List 
 int getLength(struct Node *head) {
 int count = 0;
 struct Node *temp = head;
 
 while (temp != NULL) {
 count++;
 temp = temp->next;
 }
 
 return count;
}

// Recursive version
int getLengthRecursive(struct Node *head) {
 if (head == NULL) {
 return 0;
 }
 return 1 + getLengthRecursive(head->next);
}
 
 Time Complexity: O(n)
 Space Complexity: O(1) for iterative, O(n) for recursive

4. Complete Singly Linked List Example
#include <stdio.h>
#include <stdlib.h>

// Node structure
struct Node {
 int data;
 struct Node *next;
};

// Function prototypes
void insertAtBeginning(struct Node **head, int value);
void insertAtEnd(struct Node **head, int value);
void insertAtPosition(struct Node **head, int value, int position);
void deleteFromBeginning(struct Node **head);
void deleteFromEnd(struct Node **head);
void deleteAtPosition(struct Node **head, int position);
void deleteByValue(struct Node **head, int value);
void displayList(struct Node *head);
int search(struct Node *head, int value);
int getLength(struct Node *head);
void freeList(struct Node **head);

int main() {
 struct Node *head = NULL;
 
 printf("=== Linked List Operations Demo ===\n\n");
 
 // Insert operations
 printf("Inserting elements...\n");
 insertAtBeginning(&head, 10);
 insertAtBeginning(&head, 5);
 insertAtEnd(&head, 20);
 insertAtEnd(&head, 25);
 insertAtPosition(&head, 15, 2);
 
 printf("After insertions: ");
 displayList(head);
 printf("Length: %d\n\n", getLength(head));
 
 // Search operation
 int searchValue = 15;
 int pos = search(head, searchValue);
 if (pos != -1) {
 printf("Value %d found at position %d\n\n", searchValue, pos);
 } else {
 printf("Value %d not found\n\n", searchValue);
 }
 
 // Delete operations
 printf("Deleting from beginning...\n");
 deleteFromBeginning(&head);
 displayList(head);
 
 printf("Deleting from end...\n");
 deleteFromEnd(&head);
 displayList(head);
 
 printf("Deleting at position 1...\n");
 deleteAtPosition(&head, 1);
 displayList(head);
 
 printf("Deleting value 10...\n");
 deleteByValue(&head, 10);
 displayList(head);
 
 printf("\nFinal length: %d\n", getLength(head));
 
 // Clean up
 freeList(&head);
 printf("Memory freed.\n");
 
 return 0;
}

// Function implementations
void insertAtBeginning(struct Node **head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 newNode->data = value;
 newNode->next = *head;
 *head = newNode;
}

void insertAtEnd(struct Node **head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 newNode->data = value;
 newNode->next = NULL;
 
 if (*head == NULL) {
 *head = newNode;
 return;
 }
 
 struct Node *temp = *head;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 temp->next = newNode;
}

void insertAtPosition(struct Node **head, int value, int position) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 newNode->data = value;
 
 if (position == 0) {
 newNode->next = *head;
 *head = newNode;
 return;
 }
 
 struct Node *temp = *head;
 for (int i = 0; i < position - 1 && temp != NULL; i++) {
 temp = temp->next;
 }
 
 if (temp == NULL) {
 printf("Invalid position!\n");
 free(newNode);
 return;
 }
 
 newNode->next = temp->next;
 temp->next = newNode;
}

void deleteFromBeginning(struct Node **head) {
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 struct Node *temp = *head;
 *head = (*head)->next;
 free(temp);
}

void deleteFromEnd(struct Node **head) {
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 if ((*head)->next == NULL) {
 free(*head);
 *head = NULL;
 return;
 }
 
 struct Node *temp = *head;
 while (temp->next->next != NULL) {
 temp = temp->next;
 }
 free(temp->next);
 temp->next = NULL;
}

void deleteAtPosition(struct Node **head, int position) {
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 if (position == 0) {
 struct Node *temp = *head;
 *head = (*head)->next;
 free(temp);
 return;
 }
 
 struct Node *temp = *head;
 for (int i = 0; i < position - 1 && temp != NULL; i++) {
 temp = temp->next;
 }
 
 if (temp == NULL || temp->next == NULL) {
 printf("Invalid position!\n");
 return;
 }
 
 struct Node *nodeToDelete = temp->next;
 temp->next = nodeToDelete->next;
 free(nodeToDelete);
}

void deleteByValue(struct Node **head, int value) {
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 if ((*head)->data == value) {
 struct Node *temp = *head;
 *head = (*head)->next;
 free(temp);
 return;
 }
 
 struct Node *temp = *head;
 while (temp->next != NULL && temp->next->data != value) {
 temp = temp->next;
 }
 
 if (temp->next == NULL) {
 printf("Value %d not found!\n", value);
 return;
 }
 
 struct Node *nodeToDelete = temp->next;
 temp->next = nodeToDelete->next;
 free(nodeToDelete);
}

void displayList(struct Node *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 struct Node *temp = head;
 while (temp != NULL) {
 printf("%d", temp->data);
 if (temp->next != NULL) {
 printf(" → ");
 }
 temp = temp->next;
 }
 printf(" → NULL\n");
}

int search(struct Node *head, int value) {
 struct Node *temp = head;
 int position = 0;
 
 while (temp != NULL) {
 if (temp->data == value) {
 return position;
 }
 temp = temp->next;
 position++;
 }
 return -1;
}

int getLength(struct Node *head) {
 int count = 0;
 struct Node *temp = head;
 
 while (temp != NULL) {
 count++;
 temp = temp->next;
 }
 return count;
}

void freeList(struct Node **head) {
 struct Node *temp;
 while (*head != NULL) {
 temp = *head;
 *head = (*head)->next;
 free(temp);
 }
}

5. Doubly Linked List
5.1 Node Structure 
 struct DNode {
 int data;
 struct DNode *prev; // Pointer to previous node
 struct DNode *next; // Pointer to next node
};
 
 Visual Representation: 
 NULL ← [prev|10|next] ⇄ [prev|20|next] ⇄ [prev|30|next] → NULL
 ↑
 HEAD
 
 5.2 Advantages of Doubly Linked List 
 
 Bidirectional Traversal: Can move forward and backward 
 Easy Deletion: Don't need previous node reference 
 Easier Insertion: Can insert before a node easily 
 
 Disadvantages: 
 
 More Memory: Extra pointer per node 
 Complex Operations: Must update two pointers 
 
 5.3 Basic Operations 
 5.3.1 Insert at Beginning 
 void insertAtBeginning(struct DNode **head, int value) {
 // Create new node
 struct DNode *newNode = (struct DNode*)malloc(sizeof(struct DNode));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 newNode->data = value;
 newNode->prev = NULL;
 newNode->next = *head;
 
 // Update head's prev if list not empty
 if (*head != NULL) {
 (*head)->prev = newNode;
 }
 
 *head = newNode;
}
 
 Visual Steps: 
 Initial: head → [NULL|10|●] ⇄ [●|20|NULL]

Step 1: Create newNode [NULL|5|NULL]

Step 2: Connect newNode to head
newNode: [NULL|5|●] → [NULL|10|●]

Step 3: Update head's prev
[NULL|5|●] ⇄ [●|10|●]

Step 4: Update head
head → [NULL|5|●] ⇄ [●|10|●] ⇄ [●|20|NULL]
 
 5.3.2 Insert at End 
 void insertAtEnd(struct DNode **head, int value) {
 struct DNode *newNode = (struct DNode*)malloc(sizeof(struct DNode));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 newNode->data = value;
 newNode->next = NULL;
 
 // If list is empty
 if (*head == NULL) {
 newNode->prev = NULL;
 *head = newNode;
 return;
 }
 
 // Traverse to last node
 struct DNode *temp = *head;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 
 // Insert at end
 temp->next = newNode;
 newNode->prev = temp;
}
 
 5.3.3 Delete Node 
 void deleteNode(struct DNode **head, struct DNode *nodeToDelete) {
 // Check if list or node is NULL
 if (*head == NULL || nodeToDelete == NULL) {
 return;
 }
 
 // If node is head
 if (*head == nodeToDelete) {
 *head = nodeToDelete->next;
 }
 
 // Update next's prev pointer
 if (nodeToDelete->next != NULL) {
 nodeToDelete->next->prev = nodeToDelete->prev;
 }
 
 // Update prev's next pointer
 if (nodeToDelete->prev != NULL) {
 nodeToDelete->prev->next = nodeToDelete->next;
 }
 
 free(nodeToDelete);
}
 
 5.3.4 Reverse Traversal 
 void displayReverse(struct DNode *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 // Go to last node
 struct DNode *temp = head;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 
 // Traverse backward
 printf("List (reverse): ");
 while (temp != NULL) {
 printf("%d", temp->data);
 if (temp->prev != NULL) {
 printf(" ← ");
 }
 temp = temp->prev;
 }
 printf("\n");
}

6. Circular Linked List
6.1 Structure 
 In a circular linked list, the last node points back to the first node (or head). 
 struct Node {
 int data;
 struct Node *next;
};
 
 Visual Representation: 
 ┌───────────────────────────┐
 ↓ │
HEAD → [10|●] → [20|●] → [30|●] ──┘
 
 6.2 Operations 
 6.2.1 Insert at Beginning 
 void insertAtBeginning(struct Node **head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 if (newNode == NULL) {
 printf("Memory allocation failed!\n");
 return;
 }
 
 newNode->data = value;
 
 // If list is empty
 if (*head == NULL) {
 newNode->next = newNode; // Points to itself
 *head = newNode;
 return;
 }
 
 // Find last node
 struct Node *temp = *head;
 while (temp->next != *head) {
 temp = temp->next;
 }
 
 // Insert at beginning
 newNode->next = *head;
 temp->next = newNode;
 *head = newNode;
}
 
 6.2.2 Display Circular List 
 void displayCircular(struct Node *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 struct Node *temp = head;
 printf("List: ");
 
 do {
 printf("%d", temp->data);
 temp = temp->next;
 if (temp != head) {
 printf(" → ");
 }
 } while (temp != head);
 
 printf(" → (back to %d)\n", head->data);
}
 
 6.2.3 Delete Node 
 void deleteNode(struct Node **head, int value) {
 if (*head == NULL) {
 printf("List is empty!\n");
 return;
 }
 
 struct Node *temp = *head;
 struct Node *prev = NULL;
 
 // If head node contains the value
 if (temp->data == value) {
 // If only one node
 if (temp->next == *head) {
 free(temp);
 *head = NULL;
 return;
 }
 
 // Find last node
 while (temp->next != *head) {
 temp = temp->next;
 }
 
 // Delete head
 temp->next = (*head)->next;
 free(*head);
 *head = temp->next;
 return;
 }
 
 // Search for the node
 prev = *head;
 temp = (*head)->next;
 
 while (temp != *head && temp->data != value) {
 prev = temp;
 temp = temp->next;
 }
 
 // If value not found
 if (temp == *head) {
 printf("Value %d not found!\n", value);
 return;
 }
 
 // Delete node
 prev->next = temp->next;
 free(temp);
}
 
 6.2.4 Count Nodes 
 int countNodes(struct Node *head) {
 if (head == NULL) {
 return 0;
 }
 
 int count = 1;
 struct Node *temp = head->next;
 
 while (temp != head) {
 count++;
 temp = temp->next;
 }
 
 return count;
}

7. Advanced Linked List Operations
7.1 Reverse a Singly Linked List 
 Method 1: Iterative 
 void reverseList(struct Node **head) {
 struct Node *prev = NULL;
 struct Node *current = *head;
 struct Node *next = NULL;
 
 while (current != NULL) {
 // Store next
 next = current->next;
 
 // Reverse current node's pointer
 current->next = prev;
 
 // Move pointers one position ahead
 prev = current;
 current = next;
 }
 
 *head = prev;
}
 
 Visual Steps: 
 Initial: head → [10|●] → [20|●] → [30|NULL]

Step 1: prev=NULL, current=[10], next=[20]
NULL ← [10] [20|●] → [30|NULL]

Step 2: prev=[10], current=[20], next=[30]
NULL ← [10] ← [20] [30|NULL]

Step 3: prev=[20], current=[30], next=NULL
NULL ← [10] ← [20] ← [30]

Final: head → [30|●] → [20|●] → [10|NULL]
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 Method 2: Recursive 
 struct Node* reverseRecursive(struct Node *head) {
 // Base case: empty list or single node
 if (head == NULL || head->next == NULL) {
 return head;
 }
 
 // Reverse the rest of the list
 struct Node *newHead = reverseRecursive(head->next);
 
 // Make next node point back
 head->next->next = head;
 head->next = NULL;
 
 return newHead;
}

// Wrapper function
void reverseListRecursive(struct Node **head) {
 *head = reverseRecursive(*head);
}
 
 Time Complexity: O(n)
 Space Complexity: O(n) - recursion stack 
 7.2 Find Middle Element 
 Method 1: Two-Pass 
 int findMiddle(struct Node *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return -1;
 }
 
 // Count nodes
 int count = 0;
 struct Node *temp = head;
 while (temp != NULL) {
 count++;
 temp = temp->next;
 }
 
 // Go to middle
 temp = head;
 for (int i = 0; i < count / 2; i++) {
 temp = temp->next;
 }
 
 return temp->data;
}
 
 Method 2: Slow-Fast Pointer (Optimal) 
 int findMiddleFast(struct Node *head) {
 if (head == NULL) {
 printf("List is empty!\n");
 return -1;
 }
 
 struct Node *slow = head;
 struct Node *fast = head;
 
 // Fast moves 2 steps, slow moves 1 step
 while (fast != NULL && fast->next != NULL) {
 slow = slow->next;
 fast = fast->next->next;
 }
 
 return slow->data;
}
 
 Visual Steps: 
 List: [10] → [20] → [30] → [40] → [50] → NULL

Step 1: slow=[10], fast=[10]
Step 2: slow=[20], fast=[30]
Step 3: slow=[30], fast=[50]
Step 4: fast->next=NULL, stop

Middle: 30
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 7.3 Detect Cycle (Floyd's Algorithm) 
 int hasCycle(struct Node *head) {
 if (head == NULL) {
 return 0;
 }
 
 struct Node *slow = head;
 struct Node *fast = head;
 
 while (fast != NULL && fast->next != NULL) {
 slow = slow->next;
 fast = fast->next->next;
 
 // If they meet, there's a cycle
 if (slow == fast) {
 return 1;
 }
 }
 
 return 0; // No cycle
}
 
 Visual Representation: 
 Cycle Example:
 ┌──────────────┐
 ↓ │
[10] → [20] → [30] → [40]
 ↑ ↓
 └─────┘

Without cycle:
[10] → [20] → [30] → [40] → NULL
 
 Time Complexity: O(n)
 Space Complexity: O(1) 
 7.4 Remove Duplicates from Sorted List 
 void removeDuplicates(struct Node *head) {
 if (head == NULL) {
 return;
 }
 
 struct Node *current = head;
 
 while (current->next != NULL) {
 if (current->data == current->next->data) {
 // Duplicate found
 struct Node *temp = current->next;
 current->next = temp->next;
 free(temp);
 } else {
 current = current->next;
 }
 }
}
 
 Visual Steps:

8. Practical Applications
8.1 Polynomial Addition 
 #include <stdio.h>
#include <stdlib.h>

// Node for polynomial term
struct PolyNode {
 int coeff; // Coefficient
 int exp; // Exponent
 struct PolyNode *next;
};

// Insert term in descending order of exponent
void insertTerm(struct PolyNode **poly, int coeff, int exp) {
 struct PolyNode *newNode = (struct PolyNode*)malloc(sizeof(struct PolyNode));
 newNode->coeff = coeff;
 newNode->exp = exp;
 newNode->next = NULL;
 
 if (*poly == NULL || (*poly)->exp < exp) {
 newNode->next = *poly;
 *poly = newNode;
 } else {
 struct PolyNode *temp = *poly;
 while (temp->next != NULL && temp->next->exp > exp) {
 temp = temp->next;
 }
 newNode->next = temp->next;
 temp->next = newNode;
 }
}

// Add two polynomials
struct PolyNode* addPolynomials(struct PolyNode *poly1, struct PolyNode *poly2) {
 struct PolyNode *result = NULL;
 
 while (poly1 != NULL && poly2 != NULL) {
 if (poly1->exp > poly2->exp) {
 insertTerm(&result, poly1->coeff, poly1->exp);
 poly1 = poly1->next;
 } else if (poly1->exp < poly2->exp) {
 insertTerm(&result, poly2->coeff, poly2->exp);
 poly2 = poly2->next;
 } else {
 // Same exponent - add coefficients
 int sumCoeff = poly1->coeff + poly2->coeff;
 if (sumCoeff != 0) {
 insertTerm(&result, sumCoeff, poly1->exp);
 }
 poly1 = poly1->next;
 poly2 = poly2->next;
 }
 }
 
 // Add remaining terms
 while (poly1 != NULL) {
 insertTerm(&result, poly1->coeff, poly1->exp);
 poly1 = poly1->next;
 }
 
 while (poly2 != NULL) {
 insertTerm(&result, poly2->coeff, poly2->exp);
 poly2 = poly2->next;
 }
 
 return result;
}

// Display polynomial
void displayPoly(struct PolyNode *poly) {
 if (poly == NULL) {
 printf("0\n");
 return;
 }
 
 while (poly != NULL) {
 printf("%dx^%d", poly->coeff, poly->exp);
 poly = poly->next;
 if (poly != NULL) {
 printf(" + ");
 }
 }
 printf("\n");
}

int main() {
 struct PolyNode *poly1 = NULL;
 struct PolyNode *poly2 = NULL;
 
 // Create first polynomial: 5x^3 + 4x^2 + 2
 insertTerm(&poly1, 5, 3);
 insertTerm(&poly1, 4, 2);
 insertTerm(&poly1, 2, 0);
 
 // Create second polynomial: 3x^3 + 2x + 1
 insertTerm(&poly2, 3, 3);
 insertTerm(&poly2, 2, 1);
 insertTerm(&poly2, 1, 0);
 
 printf("Polynomial 1: ");
 displayPoly(poly1);
 
 printf("Polynomial 2: ");
 displayPoly(poly2);
 
 struct PolyNode *result = addPolynomials(poly1, poly2);
 
 printf("Sum: ");
 displayPoly(result);
 
 return 0;
}

/* Output:
Polynomial 1: 5x^3 + 4x^2 + 2x^0
Polynomial 2: 3x^3 + 2x^1 + 1x^0
Sum: 8x^3 + 4x^2 + 2x^1 + 3x^0
*/
 
 8.2 Music Playlist Implementation 
 #include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Song {
 char title[100];
 char artist[100];
 int duration; // in seconds
 struct Song *next;
};

// Add song to playlist
void addSong(struct Song **playlist, const char *title, const char *artist, int duration) {
 struct Song *newSong = (struct Song*)malloc(sizeof(struct Song));
 strcpy(newSong->title, title);
 strcpy(newSong->artist, artist);
 newSong->duration = duration;
 newSong->next = NULL;
 
 if (*playlist == NULL) {
 *playlist = newSong;
 } else {
 struct Song *temp = *playlist;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 temp->next = newSong;
 }
}

// Display playlist
void displayPlaylist(struct Song *playlist) {
 if (playlist == NULL) {
 printf("Playlist is empty!\n");
 return;
 }
 
 int count = 1;
 printf("\n=== Playlist ===\n");
 while (playlist != NULL) {
 printf("%d. %s - %s (%d:%02d)\n", 
 count++, 
 playlist->title, 
 playlist->artist,
 playlist->duration / 60, 
 playlist->duration % 60);
 playlist = playlist->next;
 }
}

// Remove song by title
void removeSong(struct Song **playlist, const char *title) {
 if (*playlist == NULL) {
 printf("Playlist is empty!\n");
 return;
 }
 
 // If first song matches
 if (strcmp((*playlist)->title, title) == 0) {
 struct Song *temp = *playlist;
 *playlist = (*playlist)->next;
 free(temp);
 printf("Song removed: %s\n", title);
 return;
 }
 
 // Search for song
 struct Song *temp = *playlist;
 while (temp->next != NULL && strcmp(temp->next->title, title) != 0) {
 temp = temp->next;
 }
 
 if (temp->next == NULL) {
 printf("Song not found: %s\n", title);
 return;
 }
 
 struct Song *toRemove = temp->next;
 temp->next = toRemove->next;
 free(toRemove);
 printf("Song removed: %s\n", title);
}

// Calculate total duration
int totalDuration(struct Song *playlist) {
 int total = 0;
 while (playlist != NULL) {
 total += playlist->duration;
 playlist = playlist->next;
 }
 return total;
}

int main() {
 struct Song *myPlaylist = NULL;
 
 // Add songs
 addSong(&myPlaylist, "Bohemian Rhapsody", "Queen", 354);
 addSong(&myPlaylist, "Stairway to Heaven", "Led Zeppelin", 482);
 addSong(&myPlaylist, "Hotel California", "Eagles", 391);
 addSong(&myPlaylist, "Imagine", "John Lennon", 183);
 
 displayPlaylist(myPlaylist);
 
 int total = totalDuration(myPlaylist);
 printf("\nTotal duration: %d:%02d\n", total / 60, total % 60);
 
 // Remove a song
 removeSong(&myPlaylist, "Hotel California");
 
 displayPlaylist(myPlaylist);
 
 return 0;
}
 
 8.3 Browser History (Back/Forward Navigation) 
 #include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Page {
 char url[200];
 struct Page *prev;
 struct Page *next;
};

struct Browser {
 struct Page *current;
};

// Visit new page
void visitPage(struct Browser *browser, const char *url) {
 struct Page *newPage = (struct Page*)malloc(sizeof(struct Page));
 strcpy(newPage->url, url);
 newPage->next = NULL;
 
 if (browser->current != NULL) {
 // Clear forward history
 struct Page *temp = browser->current->next;
 while (temp != NULL) {
 struct Page *toDelete = temp;
 temp = temp->next;
 free(toDelete);
 }
 
 browser->current->next = newPage;
 newPage->prev = browser->current;
 } else {
 newPage->prev = NULL;
 }
 
 browser->current = newPage;
 printf("Visited: %s\n", url);
}

// Go back
void goBack(struct Browser *browser) {
 if (browser->current == NULL || browser->current->prev == NULL) {
 printf("Cannot go back!\n");
 return;
 }
 
 browser->current = browser->current->prev;
 printf("Back to: %s\n", browser->current->url);
}

// Go forward
void goForward(struct Browser *browser) {
 if (browser->current == NULL || browser->current->next == NULL) {
 printf("Cannot go forward!\n");
 return;
 }
 
 browser->current = browser->current->next;
 printf("Forward to: %s\n", browser->current->url);
}

// Display current page
void displayCurrent(struct Browser *browser) {
 if (browser->current == NULL) {
 printf("No page loaded!\n");
 } else {
 printf("Current page: %s\n", browser->current->url);
 }
}

int main() {
 struct Browser browser = {NULL};
 
 visitPage(&browser, "https://google.com");
 visitPage(&browser, "https://github.com");
 visitPage(&browser, "https://stackoverflow.com");
 
 printf("\n");
 goBack(&browser);
 goBack(&browser);
 
 printf("\n");
 goForward(&browser);
 
 printf("\n");
 visitPage(&browser, "https://reddit.com");
 
 printf("\n");
 displayCurrent(&browser);
 
 return 0;
}

9. Common Errors and Debugging
9.1 Memory Leaks 
 Problem: 
 void createList() {
 struct Node *head = (struct Node*)malloc(sizeof(struct Node));
 head->data = 10;
 head->next = NULL;
 // MEMORY LEAK! head is lost when function returns
}
 
 Solution: 
 struct Node* createList() {
 struct Node *head = (struct Node*)malloc(sizeof(struct Node));
 head->data = 10;
 head->next = NULL;
 return head; // Return pointer to caller
}

// In main:
struct Node *myList = createList();
// ... use list ...
freeList(&myList); // Free memory when done
 
 9.2 Dereferencing NULL Pointer 
 Problem: 
 void insertAtEnd(struct Node **head, int value) {
 struct Node *temp = *head;
 while (temp->next != NULL) { // CRASH if head is NULL!
 temp = temp->next;
 }
 // ...
}
 
 Solution: 
 void insertAtEnd(struct Node **head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 newNode->data = value;
 newNode->next = NULL;
 
 if (*head == NULL) { // Check for NULL first!
 *head = newNode;
 return;
 }
 
 struct Node *temp = *head;
 while (temp->next != NULL) {
 temp = temp->next;
 }
 temp->next = newNode;
}
 
 9.3 Lost Node References 
 Problem: 
 void deleteNode(struct Node **head, int position) {
 struct Node *temp = *head;
 for (int i = 0; i < position - 1; i++) {
 temp = temp->next;
 }
 temp->next = temp->next->next; // MEMORY LEAK! Node not freed
}
 
 Solution: 
 void deleteNode(struct Node **head, int position) {
 struct Node *temp = *head;
 for (int i = 0; i < position - 1; i++) {
 temp = temp->next;
 }
 struct Node *nodeToDelete = temp->next; // Save reference
 temp->next = nodeToDelete->next;
 free(nodeToDelete); // Free memory
}
 
 9.4 Infinite Loop in Circular List 
 Problem: 
 void display(struct Node *head) {
 struct Node *temp = head;
 while (temp != NULL) { // Never NULL in circular list!
 printf("%d ", temp->data);
 temp = temp->next;
 }
}
 
 Solution: 
 void displayCircular(struct Node *head) {
 if (head == NULL) return;
 
 struct Node *temp = head;
 do {
 printf("%d ", temp->data);
 temp = temp->next;
 } while (temp != head); // Check if back to head
}
 
 9.5 Incorrect Pointer Updates 
 Problem: 
 void insertAtBeginning(struct Node *head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 newNode->data = value;
 newNode->next = head;
 head = newNode; // Only changes local copy!
}
 
 Solution: 
 void insertAtBeginning(struct Node **head, int value) {
 struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
 newNode->data = value;
 newNode->next = *head;
 *head = newNode; // Updates actual head pointer
}
 
 9.6 Debugging Techniques 
 Print Node Addresses 
 void debugList(struct Node *head) {
 printf("\n=== Debug Info ===\n");
 struct Node *temp = head;
 int count = 0;
 
 while (temp != NULL) {
 printf("Node %d:\n", count++);
 printf(" Address: %p\n", (void*)temp);
 printf(" Data: %d\n", temp->data);
 printf(" Next: %p\n", (void*)temp->next);
 temp = temp->next;
 }
 printf("==================\n\n");
}
 
 Check List Integrity 
 int checkListIntegrity(struct Node *head) {
 if (head == NULL) {
 return 1; // Empty list is valid
 }
 
 struct Node *slow = head;
 struct Node *fast = head;
 
 // Check for cycles
 while (fast != NULL && fast->next != NULL) {
 slow = slow->next;
 fast = fast->next->next;
 
 if (slow == fast) {
 printf("ERROR: Cycle detected!\n");
 return 0;
 }
 }
 
 printf("List integrity: OK\n");
 return 1;
}
 
 Visualize List 
 void visualizeList(struct Node *head) {
 if (head == NULL) {
 printf("NULL\n");
 return;
 }
 
 struct Node *temp = head;
 printf("HEAD → ");
 
 while (temp != NULL) {
 printf("[%d]", temp->data);
 temp = temp->next;
 if (temp != NULL) {
 printf(" → ");
 }
 }
 printf(" → NULL\n");
}
 
 9.7 Common Error Messages 
 Segmentation Fault: 
 
 Dereferencing NULL pointer 
 Accessing freed memory 
 Buffer overflow 
 
 Memory Leak: 
 
 Not freeing allocated memory 
 Losing references to nodes 
 Not calling free() on all nodes 
 
 Infinite Loop: 
 
 Wrong termination condition 
 Circular list without proper check 
 Pointer not advancing 
 
 9.8 Preventive Measures 
 // Always check malloc return value
struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
 fprintf(stderr, "Memory allocation failed!\n");
 return NULL;
}

// Check for NULL before dereferencing
if (head != NULL) {
 // Safe to use head->data
}

// Free all nodes before exiting
void freeList(struct Node **head) {
 struct Node *temp;
 while (*head != NULL) {
 temp = *head;
 *head = (*head)->next;
 free(temp);
 }
}

// Set pointers to NULL after freeing
free(node);
node = NULL;

// Use assertions for debugging
#include <assert.h>
assert(head != NULL); // Program stops if false