Queues
Step 8 in the DSA path · 7 concepts · 6 problems
📘 Learn Queues from zero
A queue is a FIFO container (enqueue at the back, dequeue from the front). A deque (double-ended queue) lets you push/pop from both ends in O(1), which unlocks the most powerful queue trick in interviews: the monotonic deque. The canonical problem is Max of All Subarrays of Size k (LeetCode "Sliding Window Maximum"). Trigger to reach for it: when you slide a fixed-size window across an array and need the min/max of each window, and a naive recompute would be O(n·k). A deque holding indices in decreasing-value order gives the answer in O(n) total: each index is pushed and popped at most once. We trace a = [1, 3, -1, -3, 5, 3, 6, 7] with k = 3, expected output [3, 3, 5, 5, 6, 7].
✨ Added by the guide to build intuition — not from the source course.
🏗️ Visual walkthrough — trace it step by step
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 0 1 2 3 4 5 6 7
|<-- window k=3 -->|
deque (holds INDICES, front=max): [ ] empty
output: [ ]We keep a deque of indices whose values are strictly decreasing front-to-back. The front always holds the index of the current window's maximum. Two rules every step: (1) pop the front if it has slid out of the window; (2) pop from the back any index whose value is ≤ the incoming value. We record an answer once the first full window is formed (i ≥ k-1 = 2).
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 0
^ enqueue idx 0 (val 1)
deque(idx): [0] vals: [1]
output: [] (window not full yet)Deque empty, so index 0 just goes to the back. Why safe: a single element is trivially the max of what we've seen. No answer yet because we have only 1 of k=3 elements.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 0 1
^ incoming val 3
back-pop: a[0]=1 <= 3 -> drop idx 0
deque(idx): [1] vals: [3]
output: []Before pushing index 1 (value 3), we pop index 0 from the back because a[0]=1 ≤ 3. Why safe: index 0 is older and smaller, so 3 will always dominate it for every future window that could contain index 0 — it can never be a max again, so we discard it permanently.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 0 1 2
[<-- window [0..2] -->]
^F ^back (val -1)
back-pop? a[1]=3 <= -1? NO -> keep
append idx 2
deque(idx): [1, 2] vals: [3, -1]
output: [3] <- a[front=1] = 3Value -1 is smaller than the back's value (3), so we do not pop; we append index 2. Window [0..2] is now complete, so we emit a[front] = a[1] = 3. Why safe: -1 might still be the max of a later window once 3 leaves, so we keep it; the decreasing order is preserved.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 1 2 3
[<- window [1..3] ->]
^F ^back (val -3)
front in window? idx 1 >= i-k+1=1 YES
back-pop? a[2]=-1 <= -3? NO -> keep
deque(idx): [1, 2, 3] vals: [3,-1,-3]
output: [3, 3] <- a[front=1] = 3Window is now [1..3]. Front index 1 is still inside (oldest allowed index is i-k+1 = 1), so it stays. Incoming -3 is smaller than the back, so we append. Emit a[1] = 3 again. Why safe: the strictly-decreasing chain 3 > -1 > -3 means each could become a max as larger elements expire.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 2 3 4
[<- window [2..4] ->]
^ val 5
front-evict: idx 1 < i-k+1=2 -> popleft
back-pop: a[3]=-3<=5 drop, a[2]=-1<=5 drop
append idx 4
deque(idx): [4] vals: [5]
output: [3, 3, 5] <- a[4] = 5Two things fire. First, front index 1 has slid out (1 < i-k+1 = 2), so we popleft it. Then the new value 5 is bigger than everything left, so we back-pop indices 3 and 2 and push 4. Emit a[4] = 5. Why safe: the front check uses index position, so a stale max is removed exactly when its window passes; and a new large value wipes all smaller older candidates because none can ever out-max it.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 3 4 5
[<- window [3..5] ->]
^F ^back(val 3)
front in window? idx 4 >= 3 YES
back-pop? a[4]=5 <= 3? NO -> keep
deque(idx): [4, 5] vals: [5, 3]
output: [3, 3, 5, 5] <- a[4] = 5Window [3..5]. Front index 4 is in range; incoming 3 does not exceed the back value 5, so we append it as a backup. Emit a[4] = 5. Why safe: 3 sits behind 5 because if 5 expires later, 3 could be the next-best max — the deque is acting as a queue of future candidates in priority order.
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 4 5 6
[<- window[4..6]->]
^ val 6
front in window? idx 4 >= 4 YES (no evict)
back-pop: a[5]=3<=6 drop, a[4]=5<=6 drop
append idx 6
deque(idx): [6] vals: [6]
output: [3, 3, 5, 5, 6] <- a[6] = 6Value 6 beats both stored candidates, so back-pop indices 5 and 4, then push 6. The front didn't need eviction (index 4 still inside until now). Emit a[6] = 6. Why safe: a value larger than all current candidates makes every older index obsolete in O(1) amortized — each index is popped at most once across the whole run, giving O(n).
a = [ 1, 3, -1, -3, 5, 3, 6, 7]
idx 5 6 7
[<- window[5..7]->]
^ val 7
back-pop: a[6]=6 <= 7 drop
append idx 7
deque(idx): [7] vals: [7]
output: [3, 3, 5, 5, 6, 7] DONE
Windows: n-k+1 = 8-3+1 = 6 answersFinal element 7 evicts 6 from the back and becomes the lone max. Emit a[7] = 7. Final result [3, 3, 5, 5, 6, 7] with exactly n-k+1 = 6 windows. Complexity: O(n) time (every index enqueued and dequeued once) and O(k) space. This beats the naive O(n·k) rescan and the O(n·log k) heap approach — the monotonic deque is the gold-standard answer interviewers want.
🎯 Guided practice
Easy — Reverse a Queue. Given a queue
[1, 2, 3, 4](1 at front), produce a queue with the order reversed so the front becomes4.Step 1 — see the mismatch. A queue only lets you remove from the front, but reversing means the last element in must come out first. That LIFO requirement is exactly what a stack gives you. This "I need to flip FIFO order" signal → reach for a stack (or recursion, which is the call stack).
Step 2 — drain into a stack. Dequeue everything and push onto a stack: dequeue 1→push, 2→push, 3→push, 4→push. The stack top is now 4.
Step 3 — pour back. Pop from the stack and enqueue back into the queue: pop 4→enqueue, pop 3→enqueue, pop 2, pop 1. The queue is now
[4, 3, 2, 1], front = 4. Done.Complexity: O(n) time, O(n) extra space. Core pattern: a stack reverses any sequence; pairing a queue with a stack lets you re-order FIFO data.
Medium — Max of All Subarrays of Size k. Given
nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3, output the maximum of each window: expected[3, 3, 5, 5, 6, 7].Step 1 — reject the naive idea. Recomputing the max of each window is O(n·k). A max-heap improves this to O(n log k). We can do O(n) with a monotonic deque — a double-ended queue that stores indices whose values are kept in decreasing order, so the front index always points at the current window's max.
Step 2 — maintain two invariants as each index
iarrives. (a) Pop expired from the front: if the front index is≤ i - k, it has slid out of the window — remove it. (b) Pop dominated from the back: while the value at the back index is≤ nums[i], pop it — an older element no larger than a newer one can never be a future window's max. Then pushito the back. Oncei ≥ k - 1, recordnums[front]as that window's answer.Step 3 — trace it. i=0(1): deque=[0]. i=1(3): 3≥1 so pop 0, push 1 → [1]. i=2(-1): push → [1,2]; first full window, front nums[1]=3. i=3(-3): front index 1 not expired (1 > 3-k=0); push → [1,2,3]; front=3. i=4(5): pop 3,2,1 (values -3,-1,3 all ≤5), push 4 → [4]; → 5. i=5(3): push → [4,5]; → nums[4]=5. i=6(6): pop 5,4, push 6 → [6]; → 6. i=7(7): pop 6, push 7 → [7]; → 7. Result
[3,3,5,5,6,7].Complexity: O(n) time (each index is pushed and popped at most once → amortized O(1) per step), O(k) space. Core pattern: the monotonic-deque sliding-window template maintains a running extreme over a moving window in linear time — the canonical solution to Sliding Window Maximum (LeetCode 239), reused for sliding-window-minimum and bounded-difference window problems.
✨ Added by the guide — work these before the full problem set.
Lessons in this topic
- ○Introduction to Queues
- ○Working with Simple Queues
- ○Queue Implementation in Different Languages
- ○Types of Queue
- ○Diving Deeper – Circular Queues and Deques
- ○Applications and Advanced Concepts
- ○Generate Binary Numbers from 1 to N
- ○easy Reverse a Queue
- ○easy Implement Stack using Queues
- ○easy Palindrome Check using Queue
- ○medium Zigzag Iterator
- ○medium Max of All Subarrays of Size 'k'
- ○Solution Generate Binary Numbers from 1 to N