StudyHub

Algorithms · Linear structures

Deque

Push 1 and 2 at the back, then push 0 at the front: 0, 1, 2. Pop from the back: 2. Pop from the front: 0. A deque ("deck", double-ended queue) lets you add and remove at both ends, each in $O(1)$.

Why it matters: it does everything a stack and a queue do, at both ends, in $O(1)$. That makes it the tool for sliding-window problems and work-stealing schedulers.

Prerequisites

  • Queue and its circular array.
  • Stack.
  • The modulo operator %.

The analogy

Everyday analogy

Think of a deck of cards on a table. You can put a card on top or slide one under the bottom, and you can take from the top or the bottom.

A deque works the same way. It's a queue that is open at both ends, so it can act as a stack (one end) or a queue (opposite ends) or both at once.

How it works

Start with a sequence of operations by hand

Start with an empty deque. Write it front to back. Predict, then open "Check".

Example: predict before you read

Step 1. push_back 1, push_back 2. Predict the deque.

Check

1, 2, like a queue so far.

Step 2. Cover the next line and guess: after push_front 0, what is the deque?

Check

0, 1, 2. The new item goes before the old front.

Step 3. pop_back, then pop_front. Predict both returned values.

Check

pop_back returns 2 and pop_front returns 0, leaving 1.

Deque contents after push_back 1, push_back 2, push_front 0, pop_back, pop_front
What you just did by hand, front on the left. Yellow marks the end that was just used.

Quick recall: Before reading further, cover the text above and say out loud: which two operations make a deque behave like a stack?

Check your answer

push_back and pop_back (or push_front and pop_front): adding and removing at the same end.

Finish the picture: inside the circular array

Use an 8-slot array with front = 0, size = 0. Repeat the same operations.

  • push_back 1, push_back 2. Predict their slots.
    Check

    Slots 0 and 1, as in a queue.

  • push_front 0. Predict its slot.
    Check

    front steps back from 0 and wraps: (0 + 7) % 8 = 7. The deque, front to back, is slot 7, 0, 1 = 0, 1, 2.

Quick recall: Without scrolling up, say out loud: in a circular array, how does push_front find its slot?

Check your answer

It moves front one step backwards, wrapping from slot 0 to the last slot, and writes there.

The rule in plain words

Store items in a circular array with front and size. The back slot is (front + size) % capacity.

push_back and pop_back work at the back; push_front steps front back (wrapping), pop_front steps it forward.

Quick recall: Before you read the code, write what push_front does to front in your own words. Then check below.

Check your answer

Step front back by one, wrapping around if it was 0, store the value there, and add 1 to the size.

The code

Read this code after you have done the operations by hand. It will make more sense.

typedef struct { int data[8]; int front, size; } Deque;   /* capacity 8 */

void push_front(Deque *d, int v) {          /* caller checks size < 8 */
    d->front = (d->front + 7) % 8;           /* one step back, wrapping */
    d->data[d->front] = v;
    d->size++;
}
void push_back(Deque *d, int v) { d->data[(d->front + d->size) % 8] = v; d->size++; }
int pop_front(Deque *d) { int v = d->data[d->front]; d->front = (d->front + 1) % 8; d->size--; return v; }
int pop_back(Deque *d)  { d->size--; return d->data[(d->front + d->size) % 8]; }

Definition

What you just did, in one line: a deque (double-ended queue) supports adding and removing at both the front and the back.

flowchart LR
  PF["push_front / pop_front"] <--> F["front: 0"]
  F --- M["1"]
  M --- B["back: 2"]
  B <--> PB["push_back / pop_back"]
          
Before you scroll past this diagram, cover the right side and guess: if you only ever use push_back and pop_front, what structure do you get? (Answer: a queue.)

How fast is it?

Quick recall: Before looking at the table, predict: is reading the $i$-th item of an array deque $O(1)$ or $O(n)$?

Check your answer

$O(1)$: it's at slot (front + i) % capacity.

push_front steps back by one with wrap-around. Adding $\text{capacity} - 1$ instead of subtracting 1 keeps the index non-negative:

$$\text{front}_{\text{new}} \;=\; (\text{front} + \text{capacity} - 1) \bmod \text{capacity}$$

With capacity 8 and front 0: $(0 + 7) \bmod 8 = 7$, exactly the slot we used.

OperationCostWhy
push_front / push_back$O(1)$One write with index arithmetic
pop_front / pop_back$O(1)$One read, one index update
Read the $i$-th item (array deque)$O(1)$Slot (front + i) % capacity
Insert in the middle$O(n)$Items must shift

Warning

In C, (front - 1) % 8 is −1 when front is 0, because % can return negative values. Add the capacity first: (front + 7) % 8.

Exam tip

Be ready to say how a deque simulates both a stack (same end) and a queue (opposite ends), and that all four end operations are $O(1)$.

Common mistakes

  • Computing (front - 1) % capacity, which gives a negative index in C.
  • Updating size before reading in pop_back in the wrong order, which reads the wrong slot.
  • Forgetting to check for full or empty before pushing or popping.

Try it yourself

Exercise 1 (warm-up)

Starting empty: push_front 5, push_back 6, push_front 4. What is the deque, front to back?

Hint

push_front always becomes the new first item.

Answer

4, 5, 6.

Exercise 2 (a bit harder)

Find the maximum of every window of 3 in [1, 3, 2, 5, 4] by keeping, in a deque, indices of values in decreasing order (drop smaller values from the back, drop indices that left the window from the front). What are the maxima? Write your answer down before you open it.

Show answer

Windows [1,3,2], [3,2,5], [2,5,4] give 3, 5, 5. Each index is pushed and popped at most once, so the whole scan is $O(n)$.

Did you know?

Did you know?

C++ std::deque, Java's ArrayDeque and Python's collections.deque are all deques. Python's docs recommend deque over list for queues because list.pop(0) is $O(n)$.

Work-stealing schedulers give each CPU core its own deque: a core works from one end, and idle cores steal work from the other end.

Key points to remember

If you remember one thing from each line, remember the because.

  • All four end operations are $O(1)$ because each one touches one slot and updates one index.
  • A deque can act as a stack or a queue because it allows both same-end and opposite-end use.
  • push_front adds capacity − 1 because C's % can return a negative number for −1.
  • Random access is $O(1)$ in an array deque because item $i$ sits at (front + i) % capacity.
  • Sliding-window maximum is $O(n)$ with a deque because each index enters and leaves it at most once.

Self-check questions

Answer these in order, out loud. They follow the page, so your answers become the script of your video.

  1. How is a deque like a deck of cards on a table?
    Sample answer

    You can add or take cards from both the top and the bottom, just as a deque allows operations at both ends.

  2. Walk through push_back 1, push_back 2, push_front 0, pop_back, pop_front.
    Sample answer

    The deque becomes 1; 1, 2; 0, 1, 2. pop_back returns 2 and pop_front returns 0, leaving 1.

  3. Why does push_front compute (front + 7) % 8 instead of (front - 1) % 8?

    A good answer mentions what C does with negative numbers.

    Sample answer

    In C, -1 % 8 is −1, which is not a valid index. Adding 7 gives the same result modulo 8 but never goes negative.

  4. So what is a deque, in one sentence?
    Sample answer

    A double-ended queue that supports adding and removing at both the front and the back.

  5. Why can a deque replace both a stack and a queue?
    Sample answer

    Using one end only gives LIFO, like a stack; adding at one end and removing at the other gives FIFO, like a queue.

  6. What do the end operations and random access cost?
    Sample answer

    push and pop at either end are $O(1)$, and reading item $i$ in an array deque is $O(1)$; inserting in the middle is $O(n)$.

  7. Why is a deque used for the sliding-window maximum?
    Sample answer

    It keeps candidate indices in decreasing order of value: new items remove smaller ones from the back, and expired indices leave from the front, all in $O(1)$ each.

  8. If you had to teach this to a friend in 2 minutes, what would you leave out, and why?
    Sample answer

    I'd leave out the circular-array index arithmetic and the sliding window. I'd keep the deck-of-cards analogy, the five-operation trace, and "both ends $O(1)$, can act as stack or queue".

External resources

Videos

Review schedule

Come back after 3, 7, 14, then 30 days. Spacing beats cramming.

Feynman prompt

Explain this topic in your own words, as if teaching a beginner.

Explain it in your own words first, then record a 2–3 minute video on your phone.

25:00

Ready to record? Open your phone camera now and record a 2–3 minute explanation.

My notes

Saved only in this browser (localStorage). Nothing is uploaded.