Previous Topic
prerequisite_concept
0.88
Mastery of node structure, next pointers, and basic insertion/deletion in singly lists provides the foundation needed to understand the added complexity of doubly linked lists.

Doubly Linked Lists

data structures algorithms software engineering programming operating systems memory management
A doubly linked list is a linear data structure where each node holds a value and two references: one to the next node and one to the previous node. This bidirectional linking enables efficient insertions and deletions at both ends and in the middle of the list when a node reference is known. Doubly linked lists trade extra memory and pointer upkeep for flexible traversal and updates, making them useful in implementations like deques, LRU caches, and undo/redo histories.

What Is a Doubly Linked List?

A doubly linked list (DLL) is a sequence of nodes where each node stores:

  • data: the payload
  • prev: a reference to the previous node
  • next: a reference to the next node

Because nodes link in both directions, you can traverse forward and backward and perform insertions/deletions in O(1) time when you already have a reference to the node at the insertion/deletion point.

Node Structure

// C-like
struct Node {
    int value;
    Node* prev;
    Node* next;
};

struct List {
    Node* head;
    Node* tail;
    size_t size;
};

Maintaining head and tail pointers allows O(1) insertion/removal at both ends.

Core Operations and Complexities

  • Insert at head/tail: O(1)
  • Insert before/after a known node: O(1)
  • Delete a known node: O(1)
  • Search by value: O(n)
  • Traverse forward/backward: O(n)

Insert at Head

function push_front(list, x):
    n = new Node(x)
    n.prev = null
    n.next = list.head
    if list.head != null:
        list.head.prev = n
    else:
        list.tail = n  // list was empty
    list.head = n
    list.size++

Insert After a Given Node

function insert_after(node, x, list):
    n = new Node(x)
    n.prev = node
    n.next = node.next
    if node.next != null:
        node.next.prev = n
    else:
        list.tail = n
    node.next = n
    list.size++

Delete a Given Node

function erase(node, list):
    if node.prev != null:
        node.prev.next = node.next
    else:
        list.head = node.next
    if node.next != null:
        node.next.prev = node.prev
    else:
        list.tail = node.prev
    free(node)
    list.size--

Use Cases

  • Deques: efficient push/pop at both ends.
  • LRU caches: move accessed items to front; evict from back.
  • Undo/redo: navigate backward/forward through history.
  • Browser history: back and forward navigation.
  • Text editor buffers: cursor-local edits in gap/rope-like structures.

Trade-offs vs Other Structures

  • Vs arrays/dynamic arrays: DLLs excel at O(1) insertion/deletion with a node reference, but lack O(1) random access and have worse cache locality.
  • Vs singly linked lists: DLLs support reverse traversal and O(1) delete with only a node pointer (no predecessor search), but use extra memory and require more pointer maintenance.

Variants

  • Circular DLL: tail.next = head and head.prev = tail simplify some edge cases.
  • Sentinel (dummy) nodes: a non-data head/tail reduces null checks and unifies logic.
  • Intrusive lists: nodes embed link fields directly in payload objects to avoid extra allocation.

Common Pitfalls

  • Forgetting to update both prev and next links during insert/delete.
  • Not handling edge cases: empty list, single-node list, removing head/tail.
  • Memory management errors in manual memory languages (leaks, double frees, dangling pointers).
  • Iterator invalidation when nodes are removed or moved.

Language Considerations

  • C/C++: Manual memory management; consider RAII in C++ and use std::list for safety. Be mindful of allocator overhead and iterator invalidation rules.
  • Java: LinkedList<T> is typically a doubly linked list; garbage collection handles reclamation.
  • Python: collections.deque is a high-performance double-ended queue (not a pure DLL implementation but similar use cases). A custom DLL can be built with classes.

Example: Reverse Traversal

function traverse_backward(list):
    cur = list.tail
    while cur != null:
        visit(cur.value)
        cur = cur.prev

Testing Tips

  • After each operation, verify list invariants: correct head/tail, matching prev/next pairs, accurate size.
  • Stress test: insert/delete sequences, random operations, and checks for memory safety where applicable.

When to Use

Choose a doubly linked list when you need frequent insertions/deletions at arbitrary positions with node references, bidirectional traversal, or list reordering (move-to-front) — and you can tolerate extra pointer memory and reduced cache locality compared to arrays.


Context from Referenced By

Context from Related Topics
Pop Quiz
Topic: doubly_linked_lists
Level: 1
True or False:

Deleting a node from a doubly linked list takes O(1) time when you already hold a reference to that node.

Topic: doubly_linked_lists
Level: 1
Multiple Choice:

In a standard doubly linked list, which operation has time complexity O(n) in the worst case?

Topic: doubly_linked_lists
Level: 1
Fill in the Blank:

Using sentinel (dummy) nodes in a doubly linked list primarily reduces the need for _____.

Topic: doubly_linked_lists
Level: 2
True or False:

A doubly linked list does not provide constant-time random access by index.

Topic: doubly_linked_lists
Level: 2
Multiple Choice:

In a circular doubly linked list, which links are typically set to simplify edge cases?

Topic: doubly_linked_lists
Level: 2
Fill in the Blank:

A common pitfall when deleting a node in a doubly linked list is forgetting to update both the _____ links.

Topic: doubly_linked_lists
Level: 3
True or False:

Maintaining both head and tail pointers in a doubly linked list enables O(1) insertion at both the front and the back.

Topic: doubly_linked_lists
Level: 3
Multiple Choice:

Which application commonly leverages a doubly linked list to move recently accessed items to the front and evict the least recently used item from the back?

Topic: doubly_linked_lists
Level: 3
Fill in the Blank:

In an intrusive doubly linked list, link fields are embedded directly in the _____ objects.

Topic: doubly_linked_lists
Level: 4
True or False:

A doubly linked list supports reverse traversal by starting at the tail and repeatedly following prev pointers to reach the head.

Topic: doubly_linked_lists
Level: 4
Multiple Choice:

In a standard (non-circular) doubly linked list, which invariant must hold for any node x if y = x.next is not null?

Topic: doubly_linked_lists
Level: 4
Fill in the Blank:

Compared to arrays, doubly linked lists typically have worse cache _____ .

Topic: doubly_linked_lists
Level: 5
True or False:

Maintaining a size field on the list allows retrieving the length of a doubly linked list in constant time.

Topic: doubly_linked_lists
Level: 5
Multiple Choice:

In a standard non-circular doubly linked list without sentinels, when inserting a new node after the current tail (node.next == null), which additional update is required to maintain correct list invariants?

Topic: doubly_linked_lists
Level: 5
Fill in the Blank:

In a standard non-circular doubly linked list, removing the tail node in O(1) time requires updating tail to tail.prev and setting the new tail's _____ pointer to null.

Next Topic
used_by
0.94

Lru Caches
LRU caches maintain items in recency order using a doubly linked list alongside a hash map, enabling O(1) promotion to head on access and O(1) eviction from the tail.
implements
0.92

Deques
A deque is an abstract data type supporting efficient insertion and removal at both ends; a doubly linked list naturally provides these operations.
used_by
0.92

Lru Caches
LRU caches commonly maintain recency order with a doubly linked list so items can be moved to the head on access and evicted from the tail in O(1) time.
implements
0.9

Deques
A doubly linked list supports constant-time insertion and removal at both ends, making it a common concrete way to realize the deque abstract data type.
used_by
0.88

Lru Cache
LRU caches commonly pair a hash map with a doubly linked list to maintain recency order, enabling O(1) promotion and eviction of entries.
implements
0.88

Bidirectional Iterators
Two-way node links enable efficient forward and backward traversal, meeting the requirements of bidirectional iterator semantics.
used_by
0.78

Browser History
Browser history navigation (back and forward) is commonly implemented with a doubly linked list to allow bidirectional traversal and O(1) updates around the current page.
used_by
0.74

Undo Redo Systems
Undo/redo histories can be represented as bidirectionally traversable sequences of actions/states, which map naturally to doubly linked lists.