A list you can walk backwards
Adding a prev pointer alongside next lets you delete a node given only a pointer to it — something a singly linked list cannot do without walking from the head.
After this lesson
You should be able to
- Declare a doubly linked node with prev and next pointers.
- Insert and delete while correctly updating both directions.
- Explain the memory and complexity trade-off against a singly linked list.
Try it before you read
Try it live
head → NULL
Every node also points back — that backward arrow is what a plain singly linked list does not have.
Two pointers instead of one
A doubly linked node adds a prev pointer next to next. Now you can walk the list in either direction, and — this is the important part — given a pointer to any node, you already have a pointer to its predecessor without searching for it.
That is a real trade, not a free upgrade: every node now costs one more pointer's worth of memory, and every insertion or deletion must correctly update two links instead of one.
struct DNode {
int data;
struct DNode *prev;
struct DNode *next;
};
typedef struct DNode DNode;Deleting a known node
In a singly linked list, deleting a node you have a pointer to still requires walking from head to find its predecessor, because only the predecessor's next can be rewired to skip it. In a doubly linked list, the node itself already knows its predecessor.
Four pointer updates handle it: the predecessor's next skips forward, the successor's prev skips backward, then the node is freed. Get the order wrong and you free a node before reading the pointers you needed from it.
void delete_node(DNode *node)
{
if (node->prev != NULL)
node->prev->next = node->next;
if (node->next != NULL)
node->next->prev = node->prev;
free(node); /* read prev and next first, free last */
}Inserting after a given node
Insertion is the mirror image: build the new node's own links first, pointing correctly at its neighbours, then rewire the two neighbours to point at it. Building the new node's links before touching the neighbours avoids losing a reference partway through.
This ordering discipline — build the new piece fully before wiring it in — is the same idea as the front-insertion order from Lesson 2, now with twice as many pointers to get right.
Try it yourself
Build a doubly linked list of five values by appending each at the end, then print it forwards and then backwards using the same list.
Need a hint?
Printing backwards means walking from the last node using prev — you need a way to reach the last node first.
Check the worked solution
Backwards printing is only possible at all because of the prev pointer added in this lesson — a singly linked list has no way to do this without extra storage such as a stack. Finding the tail once by walking forward and reusing it for both the append and the backward print avoids re-walking the list from head each time.
#include <stdio.h>
#include <stdlib.h>
struct DNode { int data; struct DNode *prev; struct DNode *next; };
typedef struct DNode DNode;
DNode *append(DNode *head, int value)
{
DNode *node = malloc(sizeof(DNode));
node->data = value;
node->next = NULL;
if (head == NULL) {
node->prev = NULL;
return node;
}
DNode *tail = head;
while (tail->next != NULL) tail = tail->next;
tail->next = node;
node->prev = tail;
return head;
}
int main(void)
{
DNode *head = NULL;
int values[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++)
head = append(head, values[i]);
printf("Forward: ");
for (DNode *cur = head; cur != NULL; cur = cur->next)
printf("%d ", cur->data);
DNode *tail = head;
while (tail->next != NULL) tail = tail->next;
printf("\nBackward: ");
for (DNode *cur = tail; cur != NULL; cur = cur->prev)
printf("%d ", cur->data);
printf("\n");
return 0;
}Quick check
Given only a pointer to a node in the middle of a list, why is deletion easier in a doubly linked list than a singly linked one?
Why this lesson exists
Syllabus mapping
Doubly Linked List
Maps to course outcomes CO1, CO4.