Doubly Linked Lists
Structure of doubly linked lists
A singly linked list only lets you move forward through the nodes. A doubly linked list fixes that limitation by giving each node two pointers instead of one: one pointing to the next node and one pointing to the previous node. This bidirectional linking is what makes the structure so flexible.
Each node in a doubly linked list contains three fields:
- Data storing the node's value
- Next pointer pointing to the following node
- Previous pointer pointing to the preceding node
The list itself maintains a head pointer (pointing to the first node) and a tail pointer (pointing to the last node). The head's previous pointer is null, and the tail's next pointer is null.
Because you can traverse in both directions, doubly linked lists simplify operations that are awkward in singly linked lists. For example, if you're given only a pointer to a node you want to delete, you can reach both its neighbors directly without traversing from the head. Common real-world uses include undo/redo functionality in text editors and forward/back navigation in web browsers.

Operations in doubly linked lists
Every insertion or deletion in a doubly linked list requires updating two pointers per affected node (next and previous), compared to just one in a singly linked list. Here's how each operation works:
Insertion at the beginning:
- Create a new node
- Set the new node's
nextto the current head - Set the new node's
prevtonull - Set the old head's
prevto the new node - Update
headto point to the new node - If the list was empty, also set
tailto the new node
Insertion at the end:
- Create a new node
- Set the new node's
prevto the current tail - Set the new node's
nexttonull - Set the old tail's
nextto the new node - Update
tailto point to the new node - If the list was empty, also set
headto the new node
Insertion in the middle (after a given node ):
- Create a new node
- Set 's
nextto 'snext - Set 's
prevto - Set 's
next'sprevto - Set 's
nextto
Deletion at the beginning:
- Set
headto the current head'snext - Set the new head's
prevtonull - If the list is now empty, set
tailtonull
Deletion at the end:
- Set
tailto the current tail'sprev - Set the new tail's
nexttonull - If the list is now empty, set
headtonull
Deletion in the middle (removing node ):
- Set 's previous node's
nextto 'snext - Set 's next node's
prevto 'sprev
Traversal is straightforward: forward traversal starts at head and follows next pointers until null; backward traversal starts at tail and follows prev pointers until null. Both are .

Circular Linked Lists
Concept of circular linked lists
In a standard linked list, the last node's next pointer is null, marking the end. A circular linked list changes this: the last node's next pointer loops back to the first node, forming a continuous cycle with no null terminator.
There are two variants:
- Circular singly linked list: The last node's
nextpoints to the head. You can only traverse forward, but you can reach any node from any starting point by continuing around the loop. - Circular doubly linked list: The last node's
nextpoints to the head, and the head'sprevpoints to the last node. This gives you full bidirectional traversal with no dead ends.
Circular lists are a natural fit for problems that involve cycling through elements repeatedly. Round-robin CPU scheduling rotates through processes in a loop. Multiplayer game turns cycle through players continuously. Circular buffers use the same idea to manage fixed-size queues where old data gets overwritten.
One thing to watch for: since there's no null to signal the end, traversal logic needs a different stopping condition. You typically save a reference to your starting node and stop when you reach it again.
Doubly vs singly linked lists
Space complexity:
- Singly linked lists use one pointer per node. Doubly linked lists use two, so they consume more memory per node.
- Circular variants don't add extra memory beyond their non-circular counterparts. The last node simply reuses its existing pointer to point to the head instead of
null.
Time complexity:
| Operation | Singly Linked | Doubly Linked | Circular (Singly) | Circular (Doubly) |
|---|---|---|---|---|
| Insert at beginning | ||||
| Insert at end | * | ** | ||
| Delete at beginning | ||||
| Delete at end | ||||
| Delete given node | ||||
| Traversal | Forward only, | Both directions, | Forward only, | Both directions, |
* if you maintain a tail pointer, which singly linked lists sometimes do.
** Assumes you maintain a pointer to the last node, which is standard practice for circular singly linked lists.
Ease of implementation:
- Singly linked lists are the simplest since each operation only updates one pointer per node.
- Doubly linked lists require more careful pointer management (two pointers per update), but the payoff is deletion of any node you already have a reference to.
- Circular linked lists add complexity around maintaining the loop. You need to handle the circular connection whenever you insert or delete, and your traversal logic must detect when you've completed a full cycle to avoid infinite loops.