Insert at Position (Doubly Linked List)

Index pos is 0-based. Valid positions for a list of length n are 0 … n (insert after the last element when pos == n). Reuse insert at beginning and insert at end for the ends; in the middle, walk to the predecessor and splice—updating four pointer ends (prev and next on both neighbors).

C program

#include <stdio.h>
#include <stdlib.h>

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

static int count_nodes(struct Node *h)
{
    int c = 0;
    while (h != NULL) {
        c++;
        h = h->next;
    }
    return c;
}

void insert_begin(struct Node **head, struct Node **tail, int data);
void insert_end(struct Node **head, struct Node **tail, int data);

void insert_begin(struct Node **head, struct Node **tail, int data)
{
    struct Node *n = malloc(sizeof(struct Node));
    if (n == NULL)
        return;
    n->data = data;
    n->prev = NULL;
    n->next = *head;
    if (*head != NULL)
        (*head)->prev = n;
    else
        *tail = n;
    *head = n;
}

void insert_end(struct Node **head, struct Node **tail, int data)
{
    struct Node *n = malloc(sizeof(struct Node));
    if (n == NULL)
        return;
    n->data = data;
    n->next = NULL;
    if (*head == NULL) {
        n->prev = NULL;
        *head = *tail = n;
        return;
    }
    n->prev = *tail;
    (*tail)->next = n;
    *tail = n;
}

void insert_at(struct Node **head, struct Node **tail, int pos, int data)
{
    if (*head == NULL) {
        if (pos == 0)
            insert_begin(head, tail, data);
        return;
    }
    int n = count_nodes(*head);
    if (pos < 0 || pos > n)
        return;
    if (pos == 0) {
        insert_begin(head, tail, data);
        return;
    }
    if (pos == n) {
        insert_end(head, tail, data);
        return;
    }
    struct Node *cur = *head;
    for (int i = 0; i < pos - 1; i++)
        cur = cur->next;
    struct Node *nn = malloc(sizeof(struct Node));
    if (nn == NULL)
        return;
    nn->data = data;
    nn->next = cur->next;
    nn->prev = cur;
    cur->next->prev = nn;
    cur->next = nn;
}

void print_list(struct Node *head)
{
    while (head != NULL) {
        printf("%d ", head->data);
        head = head->next;
    }
    printf("\n");
}

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

int main(void)
{
    struct Node *head = NULL;
    struct Node *tail = NULL;

    insert_at(&head, &tail, 0, 10);
    insert_at(&head, &tail, 1, 20);
    insert_at(&head, &tail, 2, 30);

    print_list(head);   /* 10 20 30 */

    free_list(head);
    return 0;
}

Complexity

Case Time
pos == 0 or pos == nO(1)
MiddleO(pos) + four pointer writes

Output

After insert_at(&head, &tail, 0, 10), insert_at(..., 1, 20), and insert_at(..., 2, 30), then print_list(head):

10 20 30

Step-by-step execution table

Each call dispatches: empty list → insert_begin; pos == ninsert_end (append). This demo never hits the middle-insert path.

Step Operation Branch List state (forward) List state (backward) head tail
1 insert_at(0, 10) Empty → insert_begin 10 10 Node 10 Node 10
2 insert_at(1, 20) pos == ninsert_end 10 → 20 20 → 10 Node 10 Node 20
3 insert_at(2, 30) pos == ninsert_end 10 → 20 → 30 30 → 20 → 10 Node 10 Node 30

Memory layout table

Address Node data prev next Points to (prev / next)
0x1000 Node A 10 NULL 0x1008 prev: NULL, next: Node B
0x1008 Node B 20 0x1000 0x1010 prev: Node A, next: Node C
0x1010 Node C 30 0x1008 NULL prev: Node B, next: NULL

Pointer variables

  • head = 0x1000 — points to Node A (first node).
  • tail = 0x1010 — points to Node C (last node).

Traversal path for print_list(head)

Forward walk using next until NULL:

Start at head (0x1000) → Node A [10]
    ↓ (next)
Node B [20]
    ↓ (next)
Node C [30]
    ↓ (next)
NULL → Stop

Output: 10 20 30

Comparison: insert_at vs direct insert_begin / insert_end

Feature insert_at (this example) Direct insert_begin / insert_end
API One function + index pos Call the specific helper for front vs back
This main sequence Resolves to insert_begin then twice insert_end Would match with insert_begin(10) then insert_end ×2 — same final list
Middle insert Walk + four pointer updates (not exercised here) Not applicable on ends-only usage
Extra work count_nodes when list non-empty None for pure front/back code paths

Key branches inside insert_at

Condition Delegates to Purpose
*head == NULL && pos == 0 insert_begin First node in an empty list
pos == 0 (non-empty) insert_begin New front
pos == n insert_end Append after current tail
0 < pos < n Middle splice in insert_at Link nn between cur and cur->next; fix prev on both sides

Insert at Position (SLL) has the same index idea without prev updates. · Insert at Beginning (DLL) · Insert at End (DLL)