โ๏ธ Doubly Linked List
Singly linked lists can only go forward. What if you need to go backward too? Enter Doubly Linked Lists - each node has BOTH next and prev pointers! This enables O(1) operations at both ends and bidirectional traversal. Perfect for browser history, LRU cache, and deque!
๐ฏ The Superpowers!
Doubly Linked List advantages:
- โ O(1) tail delete - vs O(n) in singly linked list!
- โ O(1) delete any node - if you have reference!
- โ Bidirectional traversal - go forward AND backward!
- โ Simpler reverse - just swap pointers!
- โ ๏ธ More memory - 3 pointers vs 2 per node
Trade-off: More memory for more flexibility!
The Structure
Node with Prev Pointer
class Node:
def __init__(self, data):
self.data = data
self.next = None # Forward pointer
self.prev = None # Backward pointer!
Visual representation:
null โ [10] โ [20] โ [30] โ [40] โ null
โ โ
HEAD TAIL
Each node knows:
- Its data
- Next node (forward)
- Previous node (backward)
See It in Action
Watch bidirectional links work:
The Three Superpowers
Superpower #1: O(1) Tail Delete
Singly vs Doubly
Singly Linked List - O(n):
# Must find second-to-last node
current = head
while current.next.next: # O(n) traversal!
current = current.next
current.next = None # Remove tail
Doubly Linked List - O(1):
# Just use prev pointer!
tail = tail.prev # O(1) - instant!
tail.next = None
This is HUGE! Tail operations are now as fast as head operations!
Superpower #2: O(1) Delete Any Node
The Magic of Prev Pointer
If you have a reference to a node:
def delete_node(node):
# Update previous node's next
if node.prev:
node.prev.next = node.next
# Update next node's prev
if node.next:
node.next.prev = node.prev
# Done in O(1)!
This is IMPOSSIBLE with singly linked list!
You'd need to traverse from head to find the previous node - O(n).
Superpower #3: Bidirectional Traversal
Go Both Ways!
Forward (head to tail):
current = head
while current:
print(current.data)
current = current.next # Forward
Backward (tail to head):
current = tail
while current:
print(current.data)
current = current.prev # Backward!
Perfect for: Browser history, undo/redo, music playlists
The Complete Code
Click Run to execute your code
Singly vs Doubly Comparison
| Operation | Singly | Doubly | Winner |
|---|---|---|---|
| Insert at head | O(1) | O(1) | Tie |
| Insert at tail | O(n) | O(1) โ | Doubly! |
| Delete at head | O(1) | O(1) | Tie |
| Delete at tail | O(n) | O(1) โ | Doubly! |
| Delete specific node | O(n) | O(1) โ | Doubly! |
| Traverse backward | Impossible | O(n) โ | Doubly! |
| Memory per node | 2 pointers โ | 3 pointers | Singly |
Real-World Applications
Where Doubly Linked Lists Shine
1. Browser History
- Back button: Move to prev
- Forward button: Move to next
- Visit new page: Delete forward history, add new
2. LRU Cache
- Most recently used at head
- Least recently used at tail
- O(1) move to head when accessed
- O(1) remove from tail when full
3. Music Player Playlist
- Next song: Move forward
- Previous song: Move backward
- Remove song: O(1) delete
4. Undo/Redo System
- Undo: Move backward
- Redo: Move forward
- New action: Delete forward, add new
5. Deque (Double-Ended Queue)
- Add/remove from both ends in O(1)
- Perfect for sliding window algorithms
When to Use Each
โ Use Doubly Linked List When:
- Need bidirectional traversal: Browser history, playlists
- Need O(1) tail operations: Deque, LRU cache
- Need O(1) delete with reference: Priority queues
- Implementing deque: Add/remove from both ends
- Memory not critical: Extra pointer acceptable
โ Use Singly Linked List When:
- Only forward traversal: Simple iteration
- Memory is limited: Embedded systems
- Only head operations: Stack implementation
- Simpler is better: Less complexity needed
Common Mistakes
โ Mistake #1: Forgetting to Update Prev
# WRONG - only updates next!
new_node.next = head
head = new_node
# RIGHT - update both next and prev
new_node.next = head
head.prev = new_node # Don't forget!
head = new_node
โ Mistake #2: Not Updating Tail
# WRONG - tail not updated!
new_node.prev = tail
tail.next = new_node
# RIGHT - update tail pointer
new_node.prev = tail
tail.next = new_node
tail = new_node # Update tail!
โ Mistake #3: Memory Leaks
# WRONG - doesn't clear pointers!
node.prev.next = node.next
node.next.prev = node.prev
# RIGHT - clear deleted node's pointers
node.prev.next = node.next
node.next.prev = node.prev
node.prev = None # Clear!
node.next = None # Clear!
Interview Tips
๐ก How to Ace Doubly Linked List Questions
- โ Always update both pointers: next AND prev
- โ Don't forget head and tail: Update both when needed
- โ Mention O(1) advantages: Tail ops, delete with reference
- โ Know real-world uses: LRU cache, browser history
- โ Draw it out: Visualize bidirectional links
- โ Compare with singly: Show you understand trade-offs
Summary
Doubly Linked List Mastery
- Structure: Each node has data, next, AND prev
- Superpower #1: O(1) tail operations (vs O(n) in singly)
- Superpower #2: O(1) delete with reference
- Superpower #3: Bidirectional traversal
- Trade-off: More memory (3 pointers) for more flexibility
- Perfect for: LRU cache, browser history, deque, undo/redo
Interview tip: Doubly linked lists are EASIER for many operations! Always update both next and prev. Know when to use doubly vs singly - it's about trade-offs!
What's Next?
You've mastered doubly linked lists! Next: Circular Linked Lists - where the last node points back to the first!
Enjoying these tutorials?