A deque serves sequences that grow and shrink at both ends. A sliding window admits new elements at the back and expires old ones at the front. A work-stealing scheduler lets the owner take from one end while other threads steal from the other.

It generalizes two narrower structures. A Stack mutates one end. A Queue inserts at one end and removes from the other.

Core shape: elements → a ring buffer tracking a head index and a count (the back position derived mod capacity) → no efficient middle mutation

The interactive view keeps the deque state between actions. Push or pop at either end to watch head, count, and wrapping slots change without shifting live elements.

Visualization

The salient state is a head index and a count; the back position is derived as (head + count - 1) % capacity rather than stored. The implementation below shows how each operation wraps those indices.

Representation and Invariants

Two backings satisfy the same interface with different tradeoffs.

Growable ring buffer — one contiguous array (see Circular Buffer) plus a head index and a count. The occupied slots are head, head+1, …, head+count-1, each taken modulo capacity, so the live region can straddle the array’s physical end. PushBack writes at (head + count) % cap; PushFront moves head to (head - 1 + cap) % cap and writes there; both pops read an end slot and adjust head or count.

Doubly-linked list — a node per element with prev/next pointers and cached head/tail references. Each element also carries a heap-allocated node, and traversal chases pointers across the heap, so locality is poor.

Invariants that define a valid state (ring-buffer form):

  1. 0 <= count <= cap; when count == cap the next push must resize before writing.
  2. The front element is at head; the back element is at (head + count - 1) % cap. No slot outside that range holds a live element.
  3. head always stays in [0, cap); every index derived from it is taken modulo capacity, so the region wraps rather than overflowing.
  4. A pop clears its released slot (default!) so the array does not keep referenced objects alive after the deque releases them.

The head index and count are internal identity, not domain values: a resize renumbers every physical slot while preserving the logical front-to-back order.

Complexity
n
number of elements currently stored in the deque

When the Structure Stops Fitting

The middle is the hard boundary. Both backings optimize the ends: the ring stores head and count, while the linked form caches its first and last nodes. Repeated middle splices at positions already held fit a plain doubly-linked list with retained node references, or a balanced tree. A deque does not preserve that access path.

Ring-buffer resize is a latency boundary. The push that overflows capacity allocates a larger array and copies every live element before it returns. That pause can miss a real-time or per-frame deadline. Pre-sizing removes known growth points. A linked backing avoids bulk copies when even one long operation is unacceptable.

A raw deque does not produce a sliding-window maximum. That algorithm needs a monotonic deque, covered in Monotonic Stack and Queue. Each push removes dominated candidates from the back, and expired indices leave from the front. The ordering invariant belongs to the algorithm, not the container.

Diagram and C# Implementation

References