Doubly Linked List
A Doubly Linked List is an advanced variation of the linked list where each node contains data and two pointers - one to the next node and another to the previous node. This bidirectional linkage enables traversal in both directions.
The list maintains head and tail pointers, allowing O(1) operations at both ends. Each node's previous pointer forms the backward chain, while the next pointer forms the forward chain.
Doubly linked lists are particularly useful when you need frequent backward traversal or operations at both ends of the list, providing more flexibility than singly linked lists at the cost of slightly higher memory overhead.
Key Property: Each node is represented as [prev|data|next], showing the bidirectional links between nodes.
Basic Operations
Operation | Complexity | Description |
---|---|---|
Insertion at Head | O(1) | Add new node at beginning, update head and adjacent node's pointers |
Insertion at Tail | O(1) | Add new node at end using tail pointer |
Insertion at Position | O(n) | Traverse to position and insert with pointer updates |
Deletion at Head | O(1) | Remove first node and update head pointer |
Deletion at Tail | O(1) | Remove last node using tail pointer |
Deletion by Value | O(n) | Traverse to find node and update adjacent pointers |
Forward Traversal | O(n) | Traverse from head to tail using next pointers |
Backward Traversal | O(n) | Traverse from tail to head using prev pointers |
Implementation
class DoublyNode { constructor(data) { this.data = data; this.prev = null; this.next = null; }}class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.size = 0; } isEmpty() { return this.head === null; } // Insert at head insertFirst(data) { const newNode = new DoublyNode(data); if (this.isEmpty()) { this.head = newNode; this.tail = newNode; } else { newNode.next = this.head; this.head.prev = newNode; this.head = newNode; } this.size++; }
Insertion Process
- 1. Create new node with data, prev, and next pointers
- 2. For head insertion: Set new node's next to current head
- 3. Update current head's prev to new node
- 4. Move head pointer to new node
- 5. For empty list, set both head and tail to new node
- 6. For tail insertion: Similar steps but working from tail
Deletion Process
- 1. Check if list is empty
- 2. For head deletion: Store head reference, move head to head.next
- 3. Set new head's prev to null (if exists)
- 4. For tail deletion: Similar steps working from tail
- 5. For middle deletion: Find node, update adjacent nodes' pointers
- 6. Handle special cases (single node removal)
Operation Visualization
Operation | List State |
---|---|
Initialization | head → null ← tail |
insertFirst(10) | head → [null|10|•] ← tail |
insertFirst(20) | head → [null|20|•] ↔ [•|10|•] ← tail |
insertLast(30) | head → [null|20|•] ↔ [•|10|•] ↔ [•|30|null] ← tail |
deleteFirst() | head → [null|10|•] ↔ [•|30|null] ← tail |
deleteLast() | head → [null|10|null] ← tail |
Comparison with Singly Linked List
Feature | Singly Linked List | Doubly Linked List |
---|---|---|
Traversal Direction | Forward only | Both directions |
Memory Overhead | Lower (1 pointer/node) | Higher (2 pointers/node) |
Insert/Delete at Head | O(1) | O(1) |
Insert/Delete at Tail | O(n) (or O(1) with tail pointer) | O(1) |
Delete Current Node | Requires previous node | Direct access via prev pointer |
Implementation Complexity | Simpler | More complex |
Pros and Cons
Advantages
- Bidirectional traversal capability
- O(1) operations at both ends
- Easier node removal (no need to track previous node)
- Better for certain algorithms (e.g., LRU cache)
Limitations
- Extra memory for prev pointers
- More pointer operations (slightly complex implementation)
- Slightly slower operations due to extra pointer updates
Applications
- Browser forward/backward navigation
- Undo/Redo functionality in software
- LRU (Least Recently Used) cache implementation
- Navigation systems with bidirectional movement
- Music/video playlists with forward/backward controls
- Text editors with cursor movement in both directions
When to Choose: Prefer doubly linked lists when you need bidirectional traversal, frequent operations at both ends, or when the ability to delete arbitrary nodes without traversal is valuable.