Insert at End (Doubly Linked List)
With a tail pointer, appending is O(1): link the old tail’s next to the new node, set the new node’s prev to the old tail, set next to NULL, then advance tail. An empty list treats the new node as both head and tail.
C program
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *prev;
struct Node *next;
};
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 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_end(&head, &tail, 10);
insert_end(&head, &tail, 20);
insert_end(&head, &tail, 30);
print_list(head); /* 10 20 30 */
free_list(head);
return 0;
}
Complexity
| Operation | Time |
|---|---|
insert_end (with tail) | O(1) |
Output
After insert_end(&head, &tail, 10), insert_end(..., 20), and insert_end(..., 30), then print_list(head):
10 20 30
Step-by-step execution table
| Step | Operation | List state (forward) | List state (backward) | head points to |
tail points to |
|---|---|---|---|---|---|
| 1 | insert_end(10) |
10 |
10 |
Node 10 |
Node 10 |
| 2 | insert_end(20) |
10 → 20 |
20 → 10 |
Node 10 |
Node 20 |
| 3 | insert_end(30) |
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_begin vs insert_end
| Feature | insert_begin (previous example) |
insert_end (this example) |
|---|---|---|
| Insertion position | At the beginning (head) |
At the end (tail) |
Final head |
Points to last inserted (10) |
Points to first inserted (10) |
Final tail |
Points to first inserted (30) |
Points to last inserted (30) |
| Node order | Reverse of insertion order | Same as insertion order |
| Time complexity | O(1) |
O(1) with tail pointer |
| Code symmetry | Updates head pointer |
Updates tail pointer |
Key operations in insert_end
| Line / idea | Operation | Purpose |
|---|---|---|
n->next = NULL |
Set next to NULL |
Marks new node as last in the chain |
if (*head == NULL) |
Check empty list | First-node case: head and tail both become n |
n->prev = *tail |
Link to current tail | Maintain backward link from new last node |
(*tail)->next = n |
Link current tail to new node | Maintain forward link into the new tail |
*tail = n |
Update tail pointer |
New last node for the next append |
The output 10 20 30 shows the list printed in the same order as insertion (10 first, then 20, then 30), demonstrating successful insertions at the end.
Unlike a singly linked list with only head, you do not need a full scan to find the last node when tail is maintained. See Insert at End (SLL) · Insert at Beginning (DLL)