Delete from Position (Middle)
Deleting at a 0-based index pos means: if pos == 0, update the head like delete-from-beginning; otherwise walk pos - 1 links to the predecessor, unlink cur->next, free it, and set cur->next to the node after the removed one. That walk is O(pos) and at worst O(n). Invalid pos (no such node) leaves the list unchanged.
C program (return head)
The demo builds 10 20 30 with insert_begin (same order as delete from beginning), then removes nodes by index: middle 20 first, then 30, then the last 10 at index 0. Use head = delete_at(head, pos); in main.
pos == 0: savehead->next,free(head), return the saved pointer.pos > 0: advance(pos - 1)times, then removecur->nextif it exists.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
struct Node *insert_begin(struct Node *head, int data)
{
struct Node *newNode = malloc(sizeof(struct Node));
if (newNode == NULL)
return head;
newNode->data = data;
newNode->next = head;
return newNode;
}
/* pos is 0-based; returns head unchanged if pos is out of range. */
struct Node *delete_at(struct Node *head, int pos)
{
if (head == NULL)
return NULL;
if (pos == 0) {
struct Node *next = head->next;
free(head);
return next;
}
struct Node *cur = head;
int i = 0;
while (cur != NULL && i < pos - 1) {
cur = cur->next;
i++;
}
if (cur == NULL || cur->next == NULL)
return head;
struct Node *toRemove = cur->next;
cur->next = toRemove->next;
free(toRemove);
return head;
}
void print_list(struct Node *head)
{
struct Node *cur = head;
while (cur != NULL) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
int main(void)
{
struct Node *head = NULL;
head = insert_begin(head, 30);
head = insert_begin(head, 20);
head = insert_begin(head, 10);
print_list(head); /* 10 20 30 */
head = delete_at(head, 1); /* remove 20 at index 1 */
head = delete_at(head, 1); /* remove 30 (now at index 1) */
head = delete_at(head, 0); /* remove 10 */
print_list(head); /* empty list: newline only */
return 0;
}
Program output
First print_list shows the list. After three delete_at calls, the second print_list prints only a newline (no numbers).
Overall program flow
| Step | Operation | head points to |
List state (values) |
|---|---|---|---|
| 1 | head = NULL |
NULL |
Empty list |
| 2 | head = insert_begin(head, 30) |
0x1000 (only 30) |
30 |
| 3 | head = insert_begin(head, 20) |
0x2000 |
20 30 |
| 4 | head = insert_begin(head, 10) |
0x3000 |
10 20 30 |
| 5 | print_list(head) |
0x3000 |
Output line: 10 20 30 |
| 6 | head = delete_at(head, 1) |
Still 0x3000 |
10 30 (node 20 at 0x2000 freed) |
| 7 | head = delete_at(head, 1) |
Still 0x3000 |
10 (node 30 at 0x1000 freed) |
| 8 | head = delete_at(head, 0) |
NULL |
Empty (node 10 at 0x3000 freed) |
| 9 | print_list(head) |
NULL |
Output: newline only |
Each delete_at call in detail
Call 1: delete_at(0x3000, 1) — remove index 1 (20)
| Step | Operation | Variable | Value |
|---|---|---|---|
| 1 | pos > 0, walk pos - 1 = 0 steps | cur | Stays at head (0x3000) |
| 2 | toRemove = cur->next | toRemove | 0x2000 (data 20) |
| 3 | cur->next = toRemove->next | 0x3000->next | 0x1000 (skip 20) |
| 4 | free(toRemove) | — | Block 0x2000 released |
| 5 | return head | — | 0x3000 (unchanged) |
After call 1:
head → [10 | 0x1000] → [30 | NULL]
Call 2: delete_at(0x3000, 1) — remove index 1 (30)
| Step | Operation | Variable | Value |
|---|---|---|---|
| 1 | cur at 0x3000 (predecessor of tail) | toRemove | 0x1000 (30) |
| 2 | cur->next = NULL | 0x3000->next | NULL |
| 3 | free(toRemove) | — | Block 0x1000 released |
| 4 | return head | — | 0x3000 |
After call 2:
head → [10 | NULL]
Call 3: delete_at(0x3000, 0) — remove only node
| Step | Operation | Variable | Value |
|---|---|---|---|
| 1 | pos == 0 | next | head->next = NULL |
| 2 | free(head) | — | Block 0x3000 released |
| 3 | return next | return value | NULL |
After call 3:
head → NULL
Memory layout (example addresses)
List before any delete_at (after inserts)
| Address | Node | data |
next |
Points to |
|---|---|---|---|---|
0x3000 |
n1 (head, index 0) |
10 | 0x2000 |
n2 |
0x2000 |
n2 (index 1) |
20 | 0x1000 |
n3 |
0x1000 |
n3 (index 2) |
30 | NULL |
nowhere |
Visual representation
Before the first delete (after building with insert_begin):
head (0x3000)
│
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ data: 10 │ │ data: 20 │ │ data: 30 │
│ next: 0x2000 ┼───→│ next: 0x1000 ┼───→│ next: NULL │
└──────────────┘ └──────────────┘ └──────────────┘
n1 n2 n3
(index 0) (index 1) (index 2)
Detailed step-by-step: call 1 (delete index 1, 20)
Inside delete_at when head is 0x3000, pos == 1, and the list is 10 → 20 → 30:
| Line | Code | Before | After |
|---|---|---|---|
| 1 | if (head == NULL) |
head = 0x3000 |
Skip return NULL |
| 2 | if (pos == 0) |
pos = 1 |
Skip head-delete branch |
| 3 | cur = head; / i = 0; |
— | cur = 0x3000 |
| 4 | while (cur != NULL && i < pos - 1) |
pos - 1 = 0 |
Loop body not run; cur at predecessor |
| 5 | if (cur == NULL || cur->next == NULL) |
cur->next is 0x2000 |
Skip |
| 6 | toRemove = cur->next; |
— | toRemove = 0x2000 |
| 7 | cur->next = toRemove->next; |
0x2000->next is 0x1000 |
0x3000->next = 0x1000 |
| 8 | free(toRemove); |
— | Node 20 freed |
| 9 | return head; |
— | head still 0x3000 in main |
Complexity summary
| Operation | Time | Extra space |
|---|---|---|
delete_at |
O(pos) ≤ O(n) — walk to predecessor; index 0 is O(1) |
O(1) |
insert_begin (build demo list) |
O(1) per call |
O(1) per new node |
print_list |
O(n) |
O(1) |
| Whole program (build + print + deletes + print) | Build O(n) for n head inserts; deletes each up to O(n); prints O(n) |
O(n) nodes max before deletes |
The key takeaway is that delete at position mirrors insert at position: index 0 is delete-from-head, and otherwise you walk from head to the predecessor before unlinking and freeing.
Alternative: pass struct Node **head when pos == 0, or use a doubly linked list to delete by pointer without a full predecessor walk.