Web Analytics

โ†”๏ธ Doubly Linked List

Intermediate ~20 min read โญ Real-World Power

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

Output
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

  1. Structure: Each node has data, next, AND prev
  2. Superpower #1: O(1) tail operations (vs O(n) in singly)
  3. Superpower #2: O(1) delete with reference
  4. Superpower #3: Bidirectional traversal
  5. Trade-off: More memory (3 pointers) for more flexibility
  6. 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!

Practice: Implement a deque using doubly linked list. Try implementing LRU cache!