Stacks & Queues: LIFO and FIFO Data Structures

Introduction
Stacks and queues are two of the most fundamental and widely used data structures in computer science. They're everywhere — from your browser's back button to task scheduling systems, from expression evaluation to BFS graph traversal.
What makes them special is their restricted access model: unlike arrays where you can access any element, stacks and queues enforce a specific order of operations. This constraint is exactly what makes them powerful.
What You'll Learn
✅ Understand the LIFO and FIFO principles
✅ Implement stacks and queues using arrays and linked lists
✅ Master the Monotonic Stack pattern for interview problems
✅ Use a Deque for sliding window maximum problems
✅ Implement a queue using two stacks
✅ Solve classic interview problems step-by-step
Prerequisites
- Understanding of Big O Notation & Complexity Analysis
- Familiarity with Problem-Solving Patterns
- Completed Arrays & Strings and Linked Lists
- Basic programming knowledge in TypeScript or Python
1. Stacks — Last In, First Out (LIFO)
A stack is a linear data structure where elements are inserted and removed from the same end — called the top. The last element added is always the first to be removed.
Think of a stack of plates: you add plates to the top, and you remove plates from the top.
Core Stack Operations
| Operation | Description | Time Complexity |
|---|---|---|
push(x) | Add element to top | O(1) |
pop() | Remove and return top element | O(1) |
peek() / top() | View top element without removing | O(1) |
isEmpty() | Check if stack is empty | O(1) |
size() | Return number of elements | O(1) |
1.1 Stack Implementation — Array-Based
The simplest approach: use the end of an array as the top.
// TypeScript - Stack using Array
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
if (this.isEmpty()) throw new Error("Stack is empty");
return this.items.pop();
}
peek(): T {
if (this.isEmpty()) throw new Error("Stack is empty");
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
size(): number {
return this.items.length;
}
toString(): string {
return `[${this.items.join(", ")}] <- top`;
}
}
// Usage
const stack = new Stack<number>();
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack.toString()); // [10, 20, 30] <- top
console.log(stack.peek()); // 30
console.log(stack.pop()); // 30
console.log(stack.size()); // 2# Python - Stack using List
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.is_empty():
raise IndexError("Stack is empty")
return self.items.pop()
def peek(self):
if self.is_empty():
raise IndexError("Stack is empty")
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
def __repr__(self):
return f"{self.items} <- top"
# Usage
stack = Stack()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack) # [10, 20, 30] <- top
print(stack.peek()) # 30
print(stack.pop()) # 30
print(stack.size()) # 21.2 Stack Implementation — Linked List-Based
Using a linked list where head is always the top:
// TypeScript - Stack using Linked List
class ListNode<T> {
val: T;
next: ListNode<T> | null;
constructor(val: T) {
this.val = val;
this.next = null;
}
}
class LinkedStack<T> {
private head: ListNode<T> | null = null;
private _size = 0;
push(item: T): void {
const node = new ListNode(item);
node.next = this.head;
this.head = node;
this._size++;
}
pop(): T {
if (!this.head) throw new Error("Stack is empty");
const val = this.head.val;
this.head = this.head.next;
this._size--;
return val;
}
peek(): T {
if (!this.head) throw new Error("Stack is empty");
return this.head.val;
}
isEmpty(): boolean {
return this.head === null;
}
size(): number {
return this._size;
}
}Array vs Linked List for Stack:
| Aspect | Array-Based | Linked List-Based |
|---|---|---|
| Memory | Contiguous block, may resize | Dynamic, node overhead |
| push/pop | O(1) amortized | O(1) always |
| Cache performance | Better (locality) | Worse (scattered) |
| Practical choice | ✅ Usually preferred | For fixed-size or memory control |
2. Queues — First In, First Out (FIFO)
A queue is a linear data structure where elements are inserted at the rear (back) and removed from the front. The first element added is always the first to be removed.
Think of a checkout line at a store: the first person in line is the first to be served.
Core Queue Operations
| Operation | Description | Time Complexity |
|---|---|---|
enqueue(x) | Add element to rear | O(1) |
dequeue() | Remove and return front element | O(1) |
front() / peek() | View front element | O(1) |
isEmpty() | Check if queue is empty | O(1) |
size() | Return number of elements | O(1) |
2.1 Queue Implementation — Array-Based (Naïve)
The simplest approach uses an array but has a problem: shift() is O(n).
// TypeScript - Naive Queue (O(n) dequeue — avoid in interviews)
class NaiveQueue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item); // O(1) amortized
}
dequeue(): T | undefined {
return this.items.shift(); // ⚠️ O(n) — shifts all elements
}
}2.2 Queue Implementation — Linked List-Based (Correct)
Using a linked list with both head (front) and tail (rear) pointers gives O(1) for all operations:
// TypeScript - Queue using Linked List (O(1) for all ops)
class QueueNode<T> {
val: T;
next: QueueNode<T> | null = null;
constructor(val: T) { this.val = val; }
}
class Queue<T> {
private head: QueueNode<T> | null = null; // front
private tail: QueueNode<T> | null = null; // rear
private _size = 0;
enqueue(item: T): void {
const node = new QueueNode(item);
if (!this.tail) {
this.head = this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this._size++;
}
dequeue(): T {
if (!this.head) throw new Error("Queue is empty");
const val = this.head.val;
this.head = this.head.next;
if (!this.head) this.tail = null; // queue became empty
this._size--;
return val;
}
front(): T {
if (!this.head) throw new Error("Queue is empty");
return this.head.val;
}
isEmpty(): boolean {
return this.head === null;
}
size(): number {
return this._size;
}
}
// Usage
const q = new Queue<number>();
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
console.log(q.front()); // 10
console.log(q.dequeue()); // 10
console.log(q.front()); // 20
console.log(q.size()); // 2# Python - Queue using collections.deque (O(1) for all ops)
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
self.items.append(item) # Add to right (rear)
def dequeue(self):
if self.is_empty():
raise IndexError("Queue is empty")
return self.items.popleft() # Remove from left (front) — O(1)
def front(self):
if self.is_empty():
raise IndexError("Queue is empty")
return self.items[0]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Usage
q = Queue()
q.enqueue(10)
q.enqueue(20)
q.enqueue(30)
print(q.front()) # 10
print(q.dequeue()) # 10
print(q.front()) # 20Python tip: Always use
collections.dequefor queues. Python lists have O(n)pop(0)— never use a plain list as a queue.
2.3 Circular Queue
A circular queue reuses empty slots from dequeued elements, making it memory-efficient with fixed capacity:
// TypeScript - Circular Queue (ring buffer)
class CircularQueue<T> {
private items: (T | null)[];
private head = 0;
private tail = 0;
private _size = 0;
private capacity: number;
constructor(capacity: number) {
this.capacity = capacity;
this.items = new Array(capacity).fill(null);
}
enqueue(item: T): boolean {
if (this._size === this.capacity) return false; // full
this.items[this.tail] = item;
this.tail = (this.tail + 1) % this.capacity; // wrap around
this._size++;
return true;
}
dequeue(): T | null {
if (this._size === 0) return null; // empty
const val = this.items[this.head] as T;
this.items[this.head] = null;
this.head = (this.head + 1) % this.capacity; // wrap around
this._size--;
return val;
}
isFull(): boolean {
return this._size === this.capacity;
}
isEmpty(): boolean {
return this._size === 0;
}
}3. Deque — Double-Ended Queue
A deque (pronounced "deck") allows insertion and deletion at both ends in O(1). It generalizes both stacks and queues.
Deque Implementation
// TypeScript - Deque using Doubly Linked List
class DequeNode<T> {
val: T;
prev: DequeNode<T> | null = null;
next: DequeNode<T> | null = null;
constructor(val: T) { this.val = val; }
}
class Deque<T> {
private head: DequeNode<T> | null = null;
private tail: DequeNode<T> | null = null;
private _size = 0;
addFront(item: T): void {
const node = new DequeNode(item);
if (!this.head) {
this.head = this.tail = node;
} else {
node.next = this.head;
this.head.prev = node;
this.head = node;
}
this._size++;
}
addRear(item: T): void {
const node = new DequeNode(item);
if (!this.tail) {
this.head = this.tail = node;
} else {
node.prev = this.tail;
this.tail.next = node;
this.tail = node;
}
this._size++;
}
removeFront(): T {
if (!this.head) throw new Error("Deque is empty");
const val = this.head.val;
this.head = this.head.next;
if (this.head) this.head.prev = null;
else this.tail = null;
this._size--;
return val;
}
removeRear(): T {
if (!this.tail) throw new Error("Deque is empty");
const val = this.tail.val;
this.tail = this.tail.prev;
if (this.tail) this.tail.next = null;
else this.head = null;
this._size--;
return val;
}
peekFront(): T {
if (!this.head) throw new Error("Deque is empty");
return this.head.val;
}
peekRear(): T {
if (!this.tail) throw new Error("Deque is empty");
return this.tail.val;
}
isEmpty(): boolean { return this._size === 0; }
size(): number { return this._size; }
}# Python - Deque using collections.deque
from collections import deque
dq = deque()
dq.appendleft(10) # addFront
dq.append(20) # addRear
dq.appendleft(5) # addFront
print(list(dq)) # [5, 10, 20]
dq.popleft() # removeFront -> 5
dq.pop() # removeRear -> 20
print(list(dq)) # [10]4. Real-World Applications
Understanding when to use stacks vs queues is as important as knowing how to implement them.
Stack Applications
Queue Applications
5. Classic Interview Problems
Problem 1: Valid Parentheses (Stack)
LeetCode #20 — Given a string containing (, ), {, }, [, ], determine if it's valid.
A string is valid if:
- Every open bracket has a matching closing bracket
- Brackets close in the correct order
Key insight: Use a stack. Push opening brackets. When you see a closing bracket, check if it matches the top of the stack.
// TypeScript
function isValid(s: string): boolean {
const stack: string[] = [];
const matching: Record<string, string> = {
')': '(',
'}': '{',
']': '[',
};
for (const char of s) {
if ('({['.includes(char)) {
stack.push(char); // Push opening bracket
} else {
// Closing bracket: must match top of stack
if (stack.length === 0 || stack[stack.length - 1] !== matching[char]) {
return false;
}
stack.pop();
}
}
return stack.length === 0; // Stack must be empty at end
}
// Examples
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true# Python
def is_valid(s: str) -> bool:
stack = []
matching = {')': '(', '}': '{', ']': '['}
for char in s:
if char in '({[':
stack.append(char)
else:
if not stack or stack[-1] != matching[char]:
return False
stack.pop()
return len(stack) == 0
# Examples
print(is_valid("()")) # True
print(is_valid("()[]{}")) # True
print(is_valid("(]")) # FalseTrace through "([)]":
char='(' → push → stack=['(']
char='[' → push → stack=['(', '[']
char=')' → matching[')']='(' but top='[' → MISMATCH → return falseComplexity: Time O(n), Space O(n)
Problem 2: Evaluate Reverse Polish Notation (Stack)
LeetCode #150 — Evaluate an expression in Reverse Polish Notation (postfix). Operators: +, -, *, /.
Examples:
["2","1","+","3","*"]→((2 + 1) * 3)=9["4","13","5","/","+"]→(4 + (13 / 5))=6
// TypeScript
function evalRPN(tokens: string[]): number {
const stack: number[] = [];
const ops = new Set(['+', '-', '*', '/']);
for (const token of tokens) {
if (ops.has(token)) {
const b = stack.pop()!; // Second operand
const a = stack.pop()!; // First operand
let result: number;
if (token === '+') result = a + b;
else if (token === '-') result = a - b;
else if (token === '*') result = a * b;
else result = Math.trunc(a / b); // Truncate toward zero
stack.push(result);
} else {
stack.push(parseInt(token));
}
}
return stack[0];
}
console.log(evalRPN(["2","1","+","3","*"])); // 9
console.log(evalRPN(["4","13","5","/","+"])); // 6
console.log(evalRPN(["10","6","9","3","+","-1","*","+"])); // 12# Python
def eval_rpn(tokens: list[str]) -> int:
stack = []
ops = {'+', '-', '*', '/'}
for token in tokens:
if token in ops:
b = stack.pop()
a = stack.pop()
if token == '+': stack.append(a + b)
elif token == '-': stack.append(a - b)
elif token == '*': stack.append(a * b)
else: stack.append(int(a / b)) # Truncate toward zero
else:
stack.append(int(token))
return stack[0]Complexity: Time O(n), Space O(n)
Problem 3: Daily Temperatures — Monotonic Stack (Stack)
LeetCode #739 — Given daily temperatures, find how many days until a warmer temperature. Return 0 if no warmer day exists.
Example: [73,74,75,71,69,72,76,73] → [1,1,4,2,1,1,0,0]
Key insight: Use a monotonic decreasing stack that stores indices. When the current temperature is warmer than the stack's top, we've found the answer for that index.
// TypeScript - Monotonic Stack
function dailyTemperatures(temperatures: number[]): number[] {
const n = temperatures.length;
const result = new Array(n).fill(0);
const stack: number[] = []; // stores indices
for (let i = 0; i < n; i++) {
// While current temp is warmer than the temp at stack top
while (stack.length > 0 && temperatures[i] > temperatures[stack[stack.length - 1]]) {
const idx = stack.pop()!;
result[idx] = i - idx; // days until warmer
}
stack.push(i);
}
return result;
}
console.log(dailyTemperatures([73,74,75,71,69,72,76,73]));
// [1, 1, 4, 2, 1, 1, 0, 0]# Python
def daily_temperatures(temperatures: list[int]) -> list[int]:
n = len(temperatures)
result = [0] * n
stack = [] # stores indices
for i, temp in enumerate(temperatures):
while stack and temp > temperatures[stack[-1]]:
idx = stack.pop()
result[idx] = i - idx
stack.append(i)
return resultTrace through [73, 74, 75, 71, 69, 72, 76, 73]:
i=0, temp=73 → stack=[] → push 0 → stack=[0]
i=1, temp=74 → 74>73(idx=0) → result[0]=1-0=1, stack=[] → push 1 → stack=[1]
i=2, temp=75 → 75>74(idx=1) → result[1]=2-1=1, stack=[] → push 2 → stack=[2]
i=3, temp=71 → 71<75 → push 3 → stack=[2,3]
i=4, temp=69 → 69<71 → push 4 → stack=[2,3,4]
i=5, temp=72 → 72>69(idx=4)→result[4]=5-4=1, 72>71(idx=3)→result[3]=5-3=2, 72<75 → push 5 → stack=[2,5]
i=6, temp=76 → 76>72(idx=5)→result[5]=6-5=1, 76>75(idx=2)→result[2]=6-2=4, stack=[] → push 6 → stack=[6]
i=7, temp=73 → 73<76 → push 7 → stack=[6,7]
Final: result = [1,1,4,2,1,1,0,0]Complexity: Time O(n), Space O(n)
Problem 4: Implement Queue Using Two Stacks (Stack + Queue)
LeetCode #232 — Implement a FIFO queue using only two stacks.
Key insight: Two reversals equal the original order. Stack s1 receives pushes. Stack s2 serves pops — refill from s1 only when s2 is empty (lazy transfer).
// TypeScript
class MyQueue {
private inbox: number[] = []; // for push
private outbox: number[] = []; // for pop/peek
push(x: number): void {
this.inbox.push(x);
}
pop(): number {
this.transfer();
return this.outbox.pop()!;
}
peek(): number {
this.transfer();
return this.outbox[this.outbox.length - 1];
}
empty(): boolean {
return this.inbox.length === 0 && this.outbox.length === 0;
}
private transfer(): void {
// Only transfer when outbox is empty (lazy)
if (this.outbox.length === 0) {
while (this.inbox.length > 0) {
this.outbox.push(this.inbox.pop()!);
}
}
}
}
// Usage
const queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.push(3);
console.log(queue.peek()); // 1 (FIFO: first pushed)
console.log(queue.pop()); // 1
console.log(queue.pop()); // 2# Python
class MyQueue:
def __init__(self):
self.inbox = [] # for push
self.outbox = [] # for pop/peek
def push(self, x: int) -> None:
self.inbox.append(x)
def pop(self) -> int:
self._transfer()
return self.outbox.pop()
def peek(self) -> int:
self._transfer()
return self.outbox[-1]
def empty(self) -> bool:
return not self.inbox and not self.outbox
def _transfer(self) -> None:
if not self.outbox:
while self.inbox:
self.outbox.append(self.inbox.pop())Amortized complexity: Each element is pushed and popped from each stack at most once → O(1) amortized per operation.
Problem 5: Sliding Window Maximum — Monotonic Deque (Deque)
LeetCode #239 — Given array nums and window size k, find the maximum in each sliding window.
Brute force: O(n·k) — check every window. Can we do O(n)?
Key insight: Use a monotonic decreasing deque that stores indices. The front always holds the index of the current window maximum. Remove from rear elements smaller than the current (they can never be the maximum). Remove from front indices outside the window.
// TypeScript - Monotonic Deque
function maxSlidingWindow(nums: number[], k: number): number[] {
const result: number[] = [];
const deque: number[] = []; // stores indices, front = max
for (let i = 0; i < nums.length; i++) {
// Remove indices outside current window
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
// Remove smaller elements from rear (they can't be max)
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
deque.push(i);
// Start recording results once first window is complete
if (i >= k - 1) {
result.push(nums[deque[0]]); // front is always the max
}
}
return result;
}
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3));
// [3, 3, 5, 5, 6, 7]# Python
from collections import deque
def max_sliding_window(nums: list[int], k: int) -> list[int]:
result = []
dq = deque() # stores indices
for i, num in enumerate(nums):
# Remove out-of-window indices from front
while dq and dq[0] < i - k + 1:
dq.popleft()
# Remove smaller elements from rear
while dq and nums[dq[-1]] < num:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
return resultTrace through [1,3,-1,-3,5,3,6,7], k=3:
i=0, num=1 → deque=[0]
i=1, num=3 → 3>1 remove 0, deque=[1]
i=2, num=-1 → deque=[1,2], window=[1,3,-1] → result=[nums[1]]=3
i=3, num=-3 → deque=[1,2,3], window=[3,-1,-3] → result=[3,nums[1]]=3,3
i=4, num=5 → 5>-3 remove 3, 5>-1 remove 2, 5>3 remove 1, deque=[4], window→max=5
i=5, num=3 → deque=[4,5], window→max=nums[4]=5
i=6, num=6 → 6>3 remove 5, 6>5 remove 4, deque=[6], window→max=6
i=7, num=7 → 7>6 remove 6, deque=[7], window→max=7
result = [3, 3, 5, 5, 6, 7]Complexity: Time O(n), Space O(k)
Problem 6: Min Stack — Stack with O(1) getMin (Stack)
LeetCode #155 — Design a stack that supports push, pop, top, and getMin in O(1).
Key insight: Maintain a second "min stack" that tracks the minimum at each level.
// TypeScript
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const currentMin = this.minStack.length === 0
? val
: Math.min(val, this.minStack[this.minStack.length - 1]);
this.minStack.push(currentMin);
}
pop(): void {
this.stack.pop();
this.minStack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}
// Usage
const minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
console.log(minStack.getMin()); // -3
minStack.pop();
console.log(minStack.top()); // 0
console.log(minStack.getMin()); // -2# Python
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int) -> None:
self.stack.append(val)
current_min = val if not self.min_stack else min(val, self.min_stack[-1])
self.min_stack.append(current_min)
def pop(self) -> None:
self.stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def get_min(self) -> int:
return self.min_stack[-1]Complexity: Time O(1) for all operations, Space O(n)
Problem 7: Next Greater Element (Monotonic Stack)
LeetCode #496 — For each element in nums1, find the next greater element in nums2. If none exists, return -1.
// TypeScript - Monotonic Stack
function nextGreaterElement(nums1: number[], nums2: number[]): number[] {
// Build next-greater map for nums2
const nextGreater = new Map<number, number>();
const stack: number[] = [];
for (const num of nums2) {
while (stack.length > 0 && stack[stack.length - 1] < num) {
nextGreater.set(stack.pop()!, num);
}
stack.push(num);
}
// Remaining elements in stack have no next greater → -1 (Map default)
return nums1.map(n => nextGreater.get(n) ?? -1);
}
console.log(nextGreaterElement([4,1,2], [1,3,4,2])); // [-1,3,-1]
console.log(nextGreaterElement([2,4], [1,2,3,4])); // [3,-1]# Python
def next_greater_element(nums1: list[int], nums2: list[int]) -> list[int]:
next_greater = {}
stack = []
for num in nums2:
while stack and stack[-1] < num:
next_greater[stack.pop()] = num
stack.append(num)
return [next_greater.get(n, -1) for n in nums1]Complexity: Time O(n + m), Space O(n) where n = len(nums2), m = len(nums1)
6. Pattern Recognition Guide
Knowing which pattern to apply is half the battle in interviews.
| Problem Pattern | Use | Signal Words / Clues |
|---|---|---|
| Parenthesis matching | Stack | "valid", "balanced", brackets |
| Undo/Redo history | Stack | "undo", "back", "history" |
| Expression evaluation | Stack | postfix, operators, precedence |
| Next greater/smaller element | Monotonic Stack | "next", "previous", "warmer", "larger" |
| Process in order received | Queue | "order", "schedule", "serve" |
| BFS traversal | Queue | level-by-level, shortest path |
| Sliding window max/min | Monotonic Deque | "window maximum", "window minimum" |
| O(1) getMin/getMax | Stack + auxiliary | "constant time", "minimum", "maximum" |
| FIFO with stack ops only | Two Stacks | "queue using stacks" |
7. Complexity Summary
Stack
| Operation | Array-Based | Linked List-Based |
|---|---|---|
| push | O(1) amortized | O(1) |
| pop | O(1) amortized | O(1) |
| peek | O(1) | O(1) |
| Space | O(n) | O(n) |
Queue
| Operation | Naive Array | Linked List / deque |
|---|---|---|
| enqueue | O(1) amortized | O(1) |
| dequeue | ❌ O(n) | O(1) |
| front | O(1) | O(1) |
| Space | O(n) | O(n) |
Deque (Double-ended Queue)
| Operation | Linked List / deque |
|---|---|
| addFront | O(1) |
| addRear | O(1) |
| removeFront | O(1) |
| removeRear | O(1) |
| Space | O(n) |
8. Practice Problems
Work through these problems in order. Each one reinforces the patterns from this post.
Easy
| Problem | Key Concept | LeetCode |
|---|---|---|
| Valid Parentheses | Stack | #20 |
| Implement Stack using Queues | Two Queues | #225 |
| Implement Queue using Stacks | Two Stacks | #232 |
| Min Stack | Auxiliary Stack | #155 |
| Next Greater Element I | Monotonic Stack | #496 |
Medium
| Problem | Key Concept | LeetCode |
|---|---|---|
| Daily Temperatures | Monotonic Stack | #739 |
| Evaluate Reverse Polish Notation | Stack | #150 |
| Decode String | Stack | #394 |
| Design Circular Queue | Circular Queue | #622 |
| Next Greater Element II | Monotonic Stack + Circular | #503 |
| Number of Visible People in a Queue | Monotonic Stack | #1944 |
Hard
| Problem | Key Concept | LeetCode |
|---|---|---|
| Sliding Window Maximum | Monotonic Deque | #239 |
| Largest Rectangle in Histogram | Monotonic Stack | #84 |
| Trapping Rain Water | Monotonic Stack / Two Pointers | #42 |
Summary and Key Takeaways
Stack (LIFO):
✅ Use when you need to process elements in reverse order of arrival
✅ Perfect for undo/redo, parsing, expression evaluation, DFS
✅ Monotonic stack solves "next greater/smaller" patterns in O(n)
Queue (FIFO):
✅ Use when you need to process elements in arrival order
✅ Essential for BFS, scheduling, buffering
✅ Always use deque in Python — never list.pop(0)
Deque:
✅ Generalizes both stack and queue — add/remove from both ends
✅ Monotonic deque solves sliding window max/min in O(n)
Key Interview Patterns:
✅ See brackets → think Stack
✅ See "next greater element" → think Monotonic Stack
✅ See BFS / level-order → think Queue
✅ See sliding window max/min → think Monotonic Deque
What's Next?
You've now completed Phase 2 of the DSA series — Linear Data Structures. Next up is Phase 3: Non-Linear Data Structures, starting with the most important: Trees.
Continue the DSA series:
- Next: Trees & Binary Search Trees — Hierarchical data, BST operations, DFS/BFS traversals
- Previous: Linked Lists — Dynamic sequential data, pointer manipulation
- Series Overview: Data Structures & Algorithms Roadmap
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.