GoAlgo

GoAlgo

Doubly Linked List

List is Empty
Operations
Playback
Speed
Deep Dive

Understanding Doubly Linked Lists in Detail

A Doubly Linked List is an enhanced version of the singly linked list where each node contains three fields: the data, a pointer to the next node, and a pointer to the previous node. This bidirectional linking allows traversal in both directions — forward and backward — eliminating the major limitation of singly linked lists.

Think of a doubly linked list like a train where each car knows both the car in front and the car behind it. If you are standing in any car, you can walk forward to the engine or backward to the caboose. In a singly linked list (one-way train), if you pass your stop, you must go all the way back to the start and walk through again.

Doubly linked lists are the implementation backbone of many real-world systems: your browser's back/forward navigation, the undo/redo stack in text editors, the LRU (Least Recently Used) cache used by every major tech company, and the operating system's process scheduler. They trade extra memory (one additional pointer per node) for dramatically better flexibility.

How Doubly Linked Lists Work Internally

1

Node Structure

Each node has three fields: prev (pointer to previous node), data (the stored value), and next (pointer to next node). The head node's prev is null, and the tail node's next is null. These null values mark the boundaries of the list.

2

Head and Tail Pointers

Unlike a singly linked list that only tracks head, a doubly linked list typically maintains both head and tail pointers. This allows O(1) access to both ends, making both prepend and append operations constant time.

3

Bidirectional Traversal

You can traverse forward (using next pointers) or backward (using prev pointers) from any node. This is critical for operations like "delete this node" — given a direct reference to a node, you can unlink it in O(1) because you can reach its predecessor via prev, without traversing from the head.

4

Sentinel Nodes (Optional)

Some implementations use dummy head and tail sentinel nodes that contain no data. These eliminate edge cases: you never have to check if head or tail is null. Every real node is always between the sentinels. This technique simplifies code significantly and is commonly used in LRU cache implementations.

Memory Layout

The doubly linked list trades additional memory per node for operational flexibility. Understanding this trade-off is important for system design decisions.

Double Pointer Overhead

Each node stores two pointers (prev and next) instead of one. On a 64-bit system, that is 16 bytes of pointer overhead per node. For a list of 4-byte integers, the overhead is 400% — four times more memory spent on pointers than on actual data. This is significant for memory-constrained environments.

Same Cache Behavior as Singly Linked

Like singly linked lists, nodes are scattered across the heap. The extra prev pointer does not improve or worsen cache performance — traversal still causes frequent cache misses compared to contiguous array access.

Allocation Cost

Each node requires a separate heap allocation. However, the benefit is that deletion is clean and immediate — no shifting, no resizing, just pointer updates. Memory is freed (or garbage collected) as soon as a node is unlinked.

Core Operations Explained

The extra prev pointer makes several operations significantly faster than their singly linked list counterparts. The key advantage is O(1) deletion given a node reference.

Complexity Analysis

The doubly linked list achieves O(1) for all insertions and deletions at both ends — a significant improvement over the singly linked list's O(n) tail operations.

Operation Complexity

OperationTime
Access by IndexO(n)
SearchO(n)
Insert at HeadO(1)
Insert at TailO(1)
Insert at IndexO(n)
Delete at HeadO(1)
Delete at TailO(1)
Delete Given NodeO(1)

Space Complexity

O(n)Linear Space

A doubly linked list of n elements uses O(n) memory, but with higher per-node overhead than singly linked lists. Each node stores two pointers (16 bytes on 64-bit systems) plus the data. For 1 million 4-byte integers, total memory is ~20 MB (4 + 16 bytes per node), compared to ~12 MB for a singly linked list and ~4 MB for an array. The extra 8 bytes per node buys you O(1) tail operations and O(1) arbitrary deletion.

Common Interview Patterns

Doubly linked list problems in interviews focus on leveraging bidirectional traversal and the O(1) node removal property. The LRU Cache problem alone appears in a majority of FAANG interview loops.

LRU Cache (LeetCode 146)

Hard

Design a data structure that supports get and put in O(1). This is one of the most frequently asked system design and coding questions at Google, Meta, Amazon, and Microsoft. It combines a hash map with a doubly linked list.

Flatten a Multilevel Doubly Linked List (LeetCode 430)

Medium

Given a doubly linked list where some nodes have a child pointer to another doubly linked list, flatten all levels into a single list. Tests recursion and pointer manipulation.

Browser History (LeetCode 1472)

Medium

Implement a browser history with visit, back, and forward operations. The doubly linked list naturally models this because you need both forward and backward navigation.

Design a Text Editor (LeetCode 2296)

Hard

Implement a text editor with a cursor that supports addText, deleteText, and cursorLeft/cursorRight operations. The doubly linked list allows O(k) operations where k is the number of characters affected.

Doubly Linked List vs Singly Linked List

The doubly linked list is a strict upgrade in functionality at the cost of extra memory. Choose based on whether you need backward traversal or O(1) tail operations.

FeatureSingly Linked ListDoubly Linked List
Insert at Tail
O(n) without tail pointer
O(1) with tail pointer
Delete at Tail
O(n) — must find second-to-last
O(1) — use tail.prev
Delete Given Node
O(n) — must find predecessor
O(1) — use node.prev
Backward Traversal
Impossible
Supported via prev pointers
Memory per Node
data + 1 pointer (next)
data + 2 pointers (prev + next)
Implementation Complexity
Simpler — fewer pointers to manage
More pointers = more bugs
Use in Interviews
Reverse list, cycle detection
LRU Cache, browser history, text editors

When to Use Doubly Linked Lists

Use a doubly linked list when you need bidirectional traversal or O(1) removal of arbitrary nodes. The extra memory cost is justified when these operations are frequent.

LRU / LFU Caches

The O(1) "move to front" and "remove from back" operations make doubly linked lists the only practical choice for cache eviction policies. Every production cache (Redis, Memcached, OS page cache) uses this pattern internally.

Navigation Systems

Browser back/forward, undo/redo in editors, and music player previous/next all require bidirectional traversal. The doubly linked list models "current position with history in both directions" perfectly.

Operating System Schedulers

Process and thread schedulers use doubly linked lists to manage ready queues. When a process finishes or is preempted, it can be removed from any position in O(1) and reinserted at the correct priority position.

Common Mistakes to Avoid

Forgetting to Update Both Pointers

Every insertion and deletion must update both next and prev pointers. Forgetting one creates a list that works in one direction but is broken in the other. This is the #1 source of bugs in doubly linked list implementations.

Not Handling Head/Tail Updates

When deleting the head or tail node, you must update the respective pointer. When inserting at the front or back, you must update head or tail. Forgetting these edge cases causes null pointer errors on subsequent operations.

Circular Reference Bugs

Incorrect pointer updates can create cycles — a node whose next points forward but prev points to itself, or two nodes pointing at each other. These cause infinite loops during traversal. Always draw diagrams when debugging.

Using DLL When SLL Suffices

If you only ever insert/delete at the head and traverse forward (like a stack), a singly linked list is simpler and uses less memory. Do not add complexity without a clear benefit. In interviews, justify your choice.