Fundamentals 47 min read

Why Deque Beats Vector and List: Inside the Double‑Ended Queue’s Magic

This article explains how the C++ deque combines the random‑access speed of a vector with the constant‑time double‑ended insertions of a list by using a segmented storage architecture, detailing its internal map, iterator mechanics, core operations, performance comparisons, practical use cases, and common pitfalls.

Deepin Linux
Deepin Linux
Deepin Linux
Why Deque Beats Vector and List: Inside the Double‑Ended Queue’s Magic

What is a deque?

In many development scenarios you need to frequently add or remove data at both ends, such as implementing sliding‑window algorithms, double‑ended queue components, or the underlying containers for stacks and queues. Vector offers fast tail insertion but costly head insertion, while list provides fast double‑ended operations but poor random access. Deque fills this gap by supporting random access like a vector and O(1) double‑ended insertion/deletion like a list.

Deque internal implementation

2.1 Segmented storage architecture

Deque does not use a single contiguous memory block like vector nor a linked list like list. Instead it consists of a control map (an array of pointers) and fixed‑size buffers (segments). Each buffer holds a chunk of elements (e.g., 512 bytes in SGI STL). When a buffer fills, a new buffer is allocated and linked via the control map, avoiding large reallocations and reducing fragmentation.

deque segmented storage diagram
deque segmented storage diagram

2.2 Control map: directory system

The control map is essentially a pointer directory where each entry points to a buffer. When the map needs to grow, a new map is allocated and existing pointers are copied, which is far cheaper than moving all elements as vector does during reallocation.

2.3 Iterator cross‑boundary magic

Because deque’s storage is segmented, its iterator cannot be a simple pointer. It stores four members: cur (current element), first (buffer start), last (buffer end), and map_ptr (pointer to the map entry). When the iterator reaches a buffer boundary, it uses map_ptr to jump to the next or previous buffer, making traversal appear logically continuous.

Case 1: ++it (forward cross‑boundary) Detect that cur + 1 == last (end of current buffer). Advance map_ptr to the next buffer. Set first and last to the new buffer’s bounds. Set cur to first , completing the move.
Case 2: --it (backward cross‑boundary) Detect that cur == first (beginning of current buffer). Move map_ptr to the previous buffer. Update first and last to the previous buffer’s bounds. Set cur to last - 1 , the last element of that buffer.

Core operations: double‑ended efficiency

3.1 Double‑ended insertion / deletion (O(1))

When push_back is called, deque checks whether the tail buffer has free space. If so, the element is placed directly; otherwise a new buffer is allocated, the map is updated, and the element is inserted at the start of the new buffer. The amortized cost remains O(1). push_front works symmetrically.

<code class="language-cpp">class Deque:
    def __init__(self, buffer_size=4):
        self.buffer_size = buffer_size
        self.map = []
        self.head_buffer = 0
        self.head_pos = 0
        self.tail_buffer = 0
        self.tail_pos = -1
        self.size = 0
    def push_back(self, value):
        if self.size == 0:
            self.map.append([None] * self.buffer_size)
            self.tail_pos = 0
            self.map[self.tail_buffer][self.tail_pos] = value
            self.size += 1
            return
        if self.tail_pos == self.buffer_size - 1:
            self.map.append([None] * self.buffer_size)
            self.tail_buffer += 1
            self.tail_pos = 0
        else:
            self.tail_pos += 1
        self.map[self.tail_buffer][self.tail_pos] = value
        self.size += 1
</code>

3.2 Random access (O(1))

To access the n‑th element, deque computes the buffer index buf_idx = n / buffer_size and the offset offset = n % buffer_size, then retrieves the element via the control map. This adds one extra pointer indirection compared with vector but still runs in constant time.

<code class="language-cpp">def __getitem__(self, index):
    if index < 0 or index >= self.size:
        raise IndexError("deque index out of range")
    buf_idx = index // self.buffer_size
    offset = index % self.buffer_size
    return self.map[buf_idx][offset]
</code>

3.3 Middle operations are costly

Insertion or deletion in the middle requires shifting elements within a buffer and possibly across buffers, leading to O(n) complexity, similar to vector and far slower than list for such operations.

3.4 Capacity management

Deque does not expose a capacity() function. Its segmented design allows it to grow by adding new buffers without a large contiguous allocation, which saves memory but prevents pre‑allocation tricks like vector::reserve() for bulk inserts.

Comparison with sibling containers

Vector provides contiguous storage and excellent random access, but head insertions are O(n). List offers O(1) insertion/deletion anywhere via pointers, but random access is O(n). Deque combines random access (O(1)) with O(1) double‑ended insert/delete, making it ideal for queue‑like workloads that also need occasional indexing.

Practical use cases

Implementing a double‑ended queue for task scheduling where high‑priority tasks are pushed to the front and normal tasks to the back.

Sliding‑window algorithms that need fast removal from the front and fast addition to the back while still allowing random access to window elements.

Log buffers that keep the most recent N entries, automatically discarding the oldest when new entries arrive.

Common pitfalls

All iterators become invalid after any insertion or deletion, unlike vector where only reallocation invalidates them.

Because storage is non‑contiguous, &deque[0] cannot be used with C‑style functions that require a continuous memory block (e.g., memcpy).

Debuggers may only show the first buffer; to inspect the whole deque, iterate it or copy it into a std::vector for easier viewing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancealgorithmCData Structuredeque
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.