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: save head->next, free(head), return the saved pointer.
  • pos > 0: advance (pos - 1) times, then remove cur->next if 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

10 20 30

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
1pos > 0, walk pos - 1 = 0 stepscurStays at head (0x3000)
2toRemove = cur->nexttoRemove0x2000 (data 20)
3cur->next = toRemove->next0x3000->next0x1000 (skip 20)
4free(toRemove)Block 0x2000 released
5return head0x3000 (unchanged)

After call 1:

head → [10 | 0x1000] → [30 | NULL]

Call 2: delete_at(0x3000, 1) — remove index 1 (30)

Step Operation Variable Value
1cur at 0x3000 (predecessor of tail)toRemove0x1000 (30)
2cur->next = NULL0x3000->nextNULL
3free(toRemove)Block 0x1000 released
4return head0x3000

After call 2:

head → [10 | NULL]

Call 3: delete_at(0x3000, 0) — remove only node

Step Operation Variable Value
1pos == 0nexthead->next = NULL
2free(head)Block 0x3000 released
3return nextreturn valueNULL

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.