Double Ended Lists

data structures algorithms software engineering operating systems
A double-ended list is a linear data structure that supports efficient insertions and deletions at both the front and the back. It is commonly implemented with head and tail references and often uses linked nodes (singly or doubly linked). Double-ended lists underpin the deque (double-ended queue) abstract data type and are widely used in buffering, caching, and algorithmic techniques that require fast operations at both ends.

Overview

A double-ended list is a sequence that provides constant-time operations at both ends: adding/removing from the front and adding/removing from the back. It is typically implemented as a linked structure with explicit references to both the head and the tail. This enables efficient, predictable performance for queue-like and stack-like behaviors in the same structure.

Double-ended lists are closely related to deques (double-ended queues). In many libraries, a deque is the abstract interface, and a double-ended list is one of the concrete ways to implement that interface.

Core Operations (Typical API)

  • push_front(x): insert x at the front
  • push_back(x): insert x at the back
  • pop_front(): remove and return the front element
  • pop_back(): remove and return the back element
  • front(): read the front element without removing
  • back(): read the back element without removing
  • is_empty(), size(): status and bookkeeping
  • iteration from head to tail (and often tail to head, if doubly linked)

Time and Space Complexity

  • push_front / push_back: O(1)
  • pop_front / pop_back: O(1)
  • Search by value or index: O(n) in linked implementations
  • Space: O(n) for elements plus pointer overhead per node

Common Implementations

  • Singly linked list with head and tail: constant-time at both ends for insertion/removal at ends, but removing from the back is not O(1) unless you maintain extra bookkeeping; hence singly linked is best for push_back/pop_front patterns.
  • Doubly linked list: nodes have prev and next pointers; supports O(1) insertions and deletions at both ends and O(1) removal given a node reference.
  • Circular list: tail.next points to head; often used with sentinel nodes to simplify edge cases.
  • Array-backed ring buffer: not a linked list, but commonly used to implement a deque with excellent cache locality; capacity changes may require resizing.

Relationship to Other Concepts

  • Deque ADT: a double-ended list is a common implementation strategy for deques.
  • LRU cache: often implemented with a doubly linked list plus a hash map for O(1) updates and lookups.
  • Sliding window and BFS: benefit from O(1) operations at both ends.

Pseudocode (Doubly Linked Version)

# Node with links in both directions
class Node:
    value
    prev
    next

class DoubleEndedList:
    head
    tail
    size

    def __init__():
        head = null
        tail = null
        size = 0

    def is_empty():
        return size == 0

    def push_front(x):
        n = new Node()
        n.value = x
        n.prev = null
        n.next = head
        if head != null:
            head.prev = n
        else:
            # list was empty; new node is also the tail
            tail = n
        head = n
        size += 1

    def push_back(x):
        n = new Node()
        n.value = x
        n.next = null
        n.prev = tail
        if tail != null:
            tail.next = n
        else:
            # list was empty; new node is also the head
            head = n
        tail = n
        size += 1

    def pop_front():
        if head == null: raise Empty
        x = head.value
        head = head.next
        if head != null:
            head.prev = null
        else:
            # became empty
            tail = null
        size -= 1
        return x

    def pop_back():
        if tail == null: raise Empty
        x = tail.value
        tail = tail.prev
        if tail != null:
            tail.next = null
        else:
            # became empty
            head = null
        size -= 1
        return x

Advantages

  • Predictable O(1) operations at both ends
  • Easy splicing: constant-time concatenation or node removal when references are known (doubly linked)
  • No reallocation costs as with resizing arrays

Trade-offs and Pitfalls

  • Memory overhead: each node stores pointer fields
  • Poor cache locality compared to arrays
  • Indexing by position is O(n)
  • Edge cases: empty and single-element lists require careful handling
  • Concurrency: operations require synchronization in multithreaded contexts

When to Use

  • Implementing queues/deques where both ends are frequently modified
  • LRU caches and eviction policies
  • Undo/redo stacks that need efficient movement at both ends
  • Streaming and scheduling where elements arrive and depart from ends

Comparisons

  • Array list: O(1) random access, but insert/remove at front are O(n)
  • Singly linked list: lighter weight but less flexible at the back without extra bookkeeping
  • Array-backed deque: great cache locality and often faster in practice; may require resizing

Practice Ideas

  1. Implement a double-ended list with a sentinel node to reduce edge-case branching.
  2. Build a deque API and back it with both a doubly linked list and a ring buffer; compare performance.
  3. Combine a hash map with a doubly linked list to implement an LRU cache.

Context from Referenced By

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

A singly linked list with head and tail supports O(1) removal from the back without extra bookkeeping.

Topic: double_ended_lists
Level: 1
Multiple Choice:

Which concrete implementation is commonly used to build a deque when excellent cache locality is desired?

Topic: double_ended_lists
Level: 1
Fill in the Blank:

In a doubly linked double-ended list, deleting a node when you already have a reference to that node is _____.

Topic: double_ended_lists
Level: 2
True or False:

Searching for a value in a linked double-ended list takes O(n) time in the worst case.

Topic: double_ended_lists
Level: 2
Multiple Choice:

Which data structure combination is commonly used to implement an LRU cache with O(1) updates and lookups?

Topic: double_ended_lists
Level: 2
Fill in the Blank:

To reduce edge-case branching in a double-ended list implementation, developers often include a _____ node.

Topic: double_ended_lists
Level: 3
True or False:

An array-backed ring buffer used to implement a deque may need to resize its underlying array to accommodate additional elements.

Topic: double_ended_lists
Level: 3
Multiple Choice:

For a singly linked list that maintains both head and tail pointers but no extra bookkeeping, which operation pattern achieves O(1) time per operation?

Topic: double_ended_lists
Level: 3
Fill in the Blank:

In a circular double-ended list, tail.next typically points to the _____.

Topic: double_ended_lists
Level: 4
True or False:

Concatenating two doubly linked double-ended lists can be done in O(1) time if you have references to the first list's tail and the second list's head.

Topic: double_ended_lists
Level: 4
Multiple Choice:

When pop_back removes the only node in a doubly linked double-ended list, what should be the resulting values of the head and tail pointers?

Topic: double_ended_lists
Level: 4
Fill in the Blank:

In multithreaded contexts, operations on a double-ended list typically require _____ to ensure correctness.

Topic: double_ended_lists
Level: 5
True or False:

Compared to array-backed deques, linked double-ended lists generally exhibit poorer cache locality.

Topic: double_ended_lists
Level: 5
Multiple Choice:

In linked double-ended list implementations, which design supports efficient reverse iteration (from tail to head) in O(n) time without extra storage or modifying nodes?

Topic: double_ended_lists
Level: 5
Fill in the Blank:

Double-ended lists underpin the _____ abstract data type.

Next Topic
implements
0.95

Deque
The deque abstract data type requires constant-time insertions and deletions at both ends; double-ended lists provide a canonical concrete implementation.
implements
0.94

Deque
A double-ended list provides the concrete structure and operations (insert/remove at both ends in O(1)) that realize the deque abstract data type.
used_by
0.93

Lru Cache
An LRU cache maintains recency order with a double-ended list (deque), moving accessed items to the front and evicting from the back in O(1).
used_by
0.82

Sliding Window Technique
Sliding window algorithms often leverage a deque (backed by a double-ended list) to maintain and update window elements in amortized O(1) time, e.g., for max/min or median-in-window problems.
used_by
0.74

Breadth First Search
BFS maintains its frontier as a FIFO queue; a double-ended list (via a deque) supports O(1) enqueue at the back and dequeue from the front, making it a practical backing structure for BFS.
used_by
0.66

Undo Redo Systems
Undo/redo systems maintain an action history where new operations are appended and oldest entries may be trimmed; a deque (double-ended list) supports O(1) insertions/removals at both ends for efficient history management and bidirectional traversal.