skip to content

How does a deque give you both stack and queue behavior, and what does each of its four operations cost?

level: juniorimportance: must knowfreq 62%

answer

  1. Count the ends, then count the operations
  2. Same end twice, or opposite ends
  3. One end gives LIFO, two ends give FIFO
  4. What a plain array charges to insert at the front
  5. Amortized versus worst case per push

basics

~20 s

A deque supports push and pop at both ends, each constant time. Pushing and popping at the same end gives LIFO stack behavior; pushing at one end and popping at the other gives FIFO queue behavior.

solid answer

~40 s

A deque (double-ended queue) exposes four mutating operations — push front, push back, pop front, pop back — and all four are constant time. Restrict yourself to one end for both pushing and popping and you have a stack: the last item in is the first out. Push at one end and pop at the other and you have a queue: items leave in arrival order. That is the whole trick, and it is why a deque can replace both. The cost claim needs care, though: in a single-array backing that grows by doubling, the four operations are O(1) *amortized* — one unlucky push copies the whole buffer — while node- or block-based backings give O(1) *worst case* per operation. Neither promises indexed access to the middle; only array-backed deques offer that.

go deeper

for a junior

Be ready to name the four operations and say, without hesitating, which pair gives LIFO and which pair gives FIFO. Knowing that both ends are constant time is the point of the structure.

for a middle

Explain why a plain dynamic array is O(n) at the front and what a deque does differently — a floating head offset or a pointer at each end — and distinguish amortized from worst-case constant time.

for a senior

Show that you treat a deque as an interface with several backings: say which operations your workload actually issues, and whether you need indexed access or a per-operation latency bound before you pick one.

for a principal

Own the API question. Decide whether a shared codebase gets the full four-operation surface or narrow stack and queue facades over it, and be able to justify that call in terms of the ordering bugs the wider surface makes possible.

## What a deque is A **deque** (double-ended queue, pronounced "deck") is a linear sequence whose defining property is not what it stores but what it *costs*: you may add and remove at **either** end in constant time. The core surface is four mutating operations — push at the front, push at the back, pop from the front, pop from the back — usually joined by non-destructive peeks at each end and a size query. What makes that contract notable is that the obvious sequence structures do **not** offer it: | Structure | push back | pop back | push front | pop front | |---|---|---|---|---| | Growth-doubling dynamic array | O(1) amortized | O(1) | **O(n)** | **O(n)** | | Singly linked list (head pointer only) | **O(n)** | **O(n)** | O(1) | O(1) | | Deque | O(1) | O(1) | O(1) | O(1) | A dynamic array is slow at the front because element 0 is pinned to slot 0: inserting or removing there shifts every other element one position. A deque removes that pin. An array-backed deque stores a *head offset* and lets the occupied region float (and wrap) inside the buffer, so there is spare room at both ends; a node-based deque simply keeps a pointer to each end and links nodes in both directions. Either way, both ends are cheap. ## Recovering a stack and a queue Because a deque is unrestricted, you get the two classic disciplines by *choosing which pair of operations you use*: - **Stack (LIFO)** — push and pop at the **same** end. Push back / pop back and push front / pop front are equally valid; the items come out newest-first either way. This is what a call-frame stack or a backtracking search wants. - **Queue (FIFO)** — push at **one** end and pop at the **other**. Push back / pop front is the conventional choice, and push front / pop back is its mirror. Items leave in arrival order, which is what a breadth-first frontier or a work backlog wants. So a deque is a strict superset of both abstractions, at the same asymptotic cost. That is why it is the default recommendation when you need one of them and have no reason to prefer a specialized type. ## What "O(1)" does and does not promise here This is where the question is actually won or lost in an interview. - In a **single growable array** backing, pushes are **amortized** O(1): most cost a couple of instructions, but the push that overflows the buffer allocates a bigger one and copies every element, costing O(n). Averaged over a worst-case *sequence* of n pushes the total is O(n), hence O(1) each — but no individual push is guaranteed fast. Amortized is a statement about a sequence, not about a distribution of inputs and not about one call. - In a **node-based or fixed-block** backing, each operation touches a bounded amount of memory, so the bound is O(1) worst case per operation (block-based designs still occasionally grow a small index table). - Pops are O(1) in every reasonable backing; array-backed deques usually clear the vacated slot so the removed element is not kept reachable. ## The two things people wrongly assume **"A deque is indexable."** Only sometimes. An array-backed deque can compute the slot for logical position i from the head offset in constant time, so indexed reads are O(1). A node-based deque has to walk, so position i costs O(i). If your algorithm indexes into the middle, the backing you get matters and you should not assume. **"Insertion in the middle is cheap too."** It is not part of the contract. Middle insertion is O(n) in an array backing (shifting) and O(n) in a node backing (finding the node), even though relinking, once you already hold the node, is O(1). ## The API-surface caution A deque's flexibility has a real cost on a team: a type that offers all four operations no longer documents the ordering discipline the code depends on. If a background worker drains a shared backlog and someone "fixes" a line to pop from the back, the backlog silently turns from FIFO into LIFO — no crash, no type error, just old items starving under load. That is why many codebases keep the deque as the *implementation* and expose narrow stack-shaped or queue-shaped wrappers as the *interface*, so the restriction is enforced by the type rather than by reviewer attention. ## How to answer out loud Name the four operations, state the constant-time contract, give the two restrictions that recover LIFO and FIFO, and then volunteer the amortized-versus-worst-case distinction before you are asked. Adding "and indexing is only O(1) if it is array-backed" signals that you know a deque is an interface with several implementations, not one structure.

  • If a deque already does everything a stack and a queue do, why would a team still expose narrower stack and queue types?
    Because the narrow type encodes the invariant the code depends on. With all four operations reachable, any caller can pop from the wrong end and silently flip a backlog from FIFO to LIFO — no error, just starvation of the oldest work under load. A stack-shaped or queue-shaped wrapper over the same deque makes the discipline checkable at compile time instead of at review time, and it documents intent for the next reader.
  • Does the O(1) claim mean every individual push is fast?
    No. In a single-array backing it is amortized: the push that fills the buffer allocates a larger one and copies every element, so that one call is O(n) while the sequence of n pushes totals O(n). Node- and block-based backings give a genuine per-operation worst-case bound instead, which matters when a latency budget cares about the tail rather than the mean.
  • Can you index into the middle of a deque in constant time?
    Only if it is array-backed. There, logical position i maps to a slot by offsetting from the stored head and wrapping, so the read is O(1). A doubly linked node backing must walk from an end, making position i cost O(i). The deque contract itself only promises the two ends, so an algorithm that indexes the middle should pick the backing deliberately.

A deque is a train platform with a door at both ends: board and leave through one door and the last aboard is first off; board at one door and leave by the other and everyone exits in arrival order.

saying these in an interview costs you the question

  • Calls a deque just a queue you can also peek at from the back
  • Says pushing at the front must be O(n) like array insertion
  • Claims O(1) means every single operation is uniformly fast
  • Thinks recovering a stack needs both ends of the deque
  • Assumes every deque supports constant-time indexing

context