Queue and Deque Interview Patterns

Monotonic Deque Pattern – Sliding Window Maximum

LeetCode 239. Given an array and window size k, return the maximum in each window. Naive O(nk) using deque to get O(n).

from collections import deque

def maxSlidingWindow(nums, k):
    dq = deque()   # stores indices, front is always max
    result = []

    for i, num in enumerate(nums):
        # Remove indices outside window
        while dq and dq[0] < i - k + 1:
            dq.popleft()

        # Maintain decreasing order: remove smaller elements from back
        while dq and nums[dq[-1]] = k - 1:
            result.append(nums[dq[0]])

    return result

Key insight: if element j is in the window and nums[j] <= nums[i] where j < i, then j can never be the maximum for any future window (i is newer and at least as large). So we discard j. The deque stays monotonically decreasing, and the front is always the current maximum. Each element is pushed and popped at most once: O(n) total.

Monotonic Stack – Next Greater Element

LC 496 (Next Greater Element I) and LC 503 (circular array variant).

def nextGreaterElement(nums):
    n = len(nums)
    result = [-1] * n
    stack = []  # indices of elements waiting for their next greater

    for i in range(n):
        while stack and nums[stack[-1]] < nums[i]:
            idx = stack.pop()
            result[idx] = nums[i]
        stack.append(i)

    return result

# Circular array variant (LC 503): iterate twice, use i % n
def nextGreaterElements_circular(nums):
    n = len(nums)
    result = [-1] * n
    stack = []

    for i in range(2 * n):
        while stack and nums[stack[-1]] < nums[i % n]:
            idx = stack.pop()
            result[idx] = nums[i % n]
        if i < n:
            stack.append(i)

    return result

Stack always holds indices in increasing order of their values (monotone increasing from bottom). When a new larger element arrives, it is the “next greater” for everything smaller at the top of the stack.

Task Scheduler LC 621

Given tasks with cooldown n, find the minimum time to execute all tasks.

import heapq
from collections import Counter, deque

def leastInterval(tasks, n):
    counts = Counter(tasks)
    max_heap = [-c for c in counts.values()]
    heapq.heapify(max_heap)

    time = 0
    cooldown_queue = deque()  # (count_when_done, available_at_time)

    while max_heap or cooldown_queue:
        time += 1

        if max_heap:
            count = heapq.heappop(max_heap) + 1  # decrement (neg)
            if count < 0:
                cooldown_queue.append((count, time + n))
        else:
            # CPU idle - jump time to next available task
            time = cooldown_queue[0][1]

        if cooldown_queue and cooldown_queue[0][1] <= time:
            heapq.heappush(max_heap, cooldown_queue.popleft()[0])

    return time

O(1) math formula: result = max(len(tasks), (max_freq – 1) * (n + 1) + count_of_max_freq_tasks). The formula computes the minimum slots needed if you arrange max-frequency tasks in a grid. If other tasks fill the gaps, len(tasks) wins.

BFS with Levels

from collections import deque

def bfs_levels(root):
    if not root:
        return []
    queue = deque([root])
    result = []

    while queue:
        level_size = len(queue)  # key: snapshot size before processing level
        level = []
        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            for child in node.children:
                queue.append(child)
        result.append(level)

    return result

# Multi-source BFS: start from multiple sources simultaneously
def multi_source_bfs(grid, sources):
    queue = deque()
    visited = set()
    for r, c in sources:
        queue.append((r, c, 0))
        visited.add((r, c))

    while queue:
        r, c, dist = queue.popleft()
        for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
            nr, nc = r+dr, c+dc
            if (nr, nc) not in visited and in_bounds(grid, nr, nc):
                visited.add((nr, nc))
                queue.append((nr, nc, dist+1))

Multi-source BFS is used in: 01 Matrix (LC 542), Rotting Oranges (LC 994), Walls and Gates (LC 286). All sources are seeded into the queue at distance 0 before the main loop starts.

Circular Buffer Implementation

class MyCircularQueue:
    def __init__(self, k: int):
        self.size = k + 1  # +1 to distinguish full from empty
        self.buf = [0] * self.size
        self.head = 0  # points to first element
        self.tail = 0  # points to next write position

    def enQueue(self, value: int) -> bool:
        if self.isFull():
            return False
        self.buf[self.tail] = value
        self.tail = (self.tail + 1) % self.size
        return True

    def deQueue(self) -> bool:
        if self.isEmpty():
            return False
        self.head = (self.head + 1) % self.size
        return True

    def Front(self) -> int:
        return -1 if self.isEmpty() else self.buf[self.head]

    def Rear(self) -> int:
        return -1 if self.isEmpty() else self.buf[(self.tail - 1) % self.size]

    def isEmpty(self) -> bool:
        return self.head == self.tail

    def isFull(self) -> bool:
        return (self.tail + 1) % self.size == self.head

The classic trick: allocate k+1 slots, declare full when (tail+1)%size == head. This wastes one slot but avoids needing a separate count variable. Alternative: use a count variable and allocate exactly k slots.

Shortest Subarray with Sum >= K (LC 862)

Finding shortest subarray with sum >= K is easy with a sliding window when all values are non-negative. With negative values, a monotonic deque on prefix sums is required.

def shortestSubarray(nums, k):
    n = len(nums)
    prefix = [0] * (n + 1)
    for i in range(n):
        prefix[i+1] = prefix[i] + nums[i]

    result = float('inf')
    dq = deque()  # indices into prefix, monotone increasing by value

    for i in range(n + 1):
        # Check if prefix[i] - prefix[dq[0]] >= k
        while dq and prefix[i] - prefix[dq[0]] >= k:
            result = min(result, i - dq.popleft())

        # Maintain monotone increasing: remove indices with prefix >= prefix[i]
        while dq and prefix[dq[-1]] >= prefix[i]:
            dq.pop()

        dq.append(i)

    return result if result != float('inf') else -1

Why deque is needed: with negatives, a prefix sum can decrease and then increase. A later prefix[j] might be smaller than an earlier prefix[i], making the subarray [i,j] invalid for a left-pointer approach. The deque keeps potential left boundaries in increasing order of their prefix sum, so once prefix[i] – prefix[dq[0]] >= k, dq[0] is used and discarded (any later right boundary would give a longer subarray from that left).

Jump Game VI LC 1696

From index i you can jump to [i+1, i+k]. Maximize score sum. Classic sliding window maximum + DP.

def maxResult(nums, k):
    n = len(nums)
    dp = [float('-inf')] * n
    dp[0] = nums[0]
    dq = deque([0])  # indices, decreasing by dp value

    for i in range(1, n):
        # Remove indices outside window
        while dq and dq[0] < i - k:
            dq.popleft()

        dp[i] = dp[dq[0]] + nums[i]

        # Maintain decreasing order
        while dq and dp[dq[-1]] <= dp[i]:
            dq.pop()
        dq.append(i)

    return dp[n-1]

Pattern: whenever you have dp[i] = max(dp[i-k..i-1]) + cost(i), use a sliding window maximum deque. The deque replaces an O(k) inner loop with O(1) amortized.

Pattern Recognition Table

Problem TypeData StructureKey LeetCode Problems
Sliding window max/minMonotonic deque239, 1438, 1696
Next greater/smaller elementMonotonic stack496, 503, 739, 84, 85
Level-order traversalQueue + len snapshot102, 103, 107, 199
Multi-source shortest distanceMulti-source BFS542, 994, 286
Fixed-size FIFO with O(1) opsCircular buffer622, 641
Task scheduling with cooldownMax-heap + cooldown queue621, 358
Shortest subarray, negative valuesMonotonic deque on prefix sums862
DP with sliding window maxMonotonic deque + DP array1696, 1425, 375
Histogram largest rectangleMonotonic stack84, 85, 42
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How does the monotonic deque solve sliding window maximum in O(n)?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Maintain a deque of indices where the corresponding values are in decreasing order from front to back. When processing index i: (1) Remove from front any index j where j < i – k + 1 (outside the current window). (2) Remove from the back any index whose value is <= nums[i] – these can never be the window maximum since nums[i] will outlast them. (3) Append i to the back. (4) If i >= k-1, the current window maximum is nums[dq[0]]. Each element is pushed and popped at most once, so the total time is O(n). This is LC 239. The same pattern applies to sliding window minimum (use an increasing deque).”}},{“@type”:”Question”,”name”:”What is the difference between a monotonic stack and a monotonic deque?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A monotonic stack solves "next greater/smaller element" problems where you need to look back at the sequence. Elements are pushed and popped from one end (the top). A monotonic deque adds the ability to remove from the front, which is needed for sliding window problems where old elements must expire. Use a monotonic stack when the problem involves "nearest" relationships: next greater element (LC 496, 503), largest rectangle in histogram (LC 84), trapping rain water (LC 42). Use a monotonic deque when there is a window of size k and you need the max/min within that window (LC 239, 862, 1696).”}},{“@type”:”Question”,”name”:”How do you solve the task scheduler problem (LC 621)?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The key insight is that the minimum time is determined by the most frequent task. Formula: result = max(len(tasks), (max_freq – 1) * (n + 1) + count_of_max_freq_tasks). Example: tasks = [A,A,A,B,B,B], n=2. max_freq=3, both A and B occur 3 times. Result = max(6, 2 * 3 + 2) = max(6, 8) = 8. The formula works because the most frequent tasks create "frames" of size (n+1), with idle slots filled by other tasks or actual idle time. If enough tasks exist to fill all slots, the result is just len(tasks). Simulation with a max-heap and cooldown queue also works but O(n log n).”}},{“@type”:”Question”,”name”:”What is multi-source BFS and when do you use it?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Multi-source BFS initializes the BFS queue with ALL source nodes simultaneously at level 0, then runs a single BFS pass to find the shortest distance from each cell to the nearest source. This is more efficient than running BFS from each source separately. Time complexity: O(rows * cols) – same as single-source BFS. Use it when the problem asks for the distance from each cell to the nearest member of a set: LC 542 (distance to nearest 0), LC 994 (time until all oranges rot), LC 1162 (height map – distance from water). Implementation: add all sources to the deque before the first iteration, using distance 0 for all of them.”}},{“@type”:”Question”,”name”:”When should you use a deque-based DP optimization?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use a monotonic deque to optimize DP transitions of the form: dp[i] = max(dp[j]) + cost(i) for j in [i-k, i-1]. The deque maintains the running maximum of dp[j] in the window, reducing each O(k) transition to O(1). Recognizable by the pattern "jump from any of the previous k positions and take the best." LC 1696 (Jump Game VI): dp[i] = max(dp[i-k..i-1]) + nums[i] – O(n) with deque vs O(nk) brute force. LC 1499 (Max Value of Equation): involves a sliding window with a combined score. The deque replaces a sorted structure when the window is contiguous and has a fixed maximum size k.”}}]}

Meta coding interviews test monotonic deque and BFS patterns. See common questions for Meta interview: queue and sliding window problems.

Apple coding rounds test queue-based algorithms and sliding window. Review patterns for Apple interview: queue and deque data structure problems.

Snowflake engineering interviews test data structure implementations. See patterns for Snowflake interview: queue and data structure problems.

Scroll to Top