π― JavaScript Stack & Queue β Top 25 Interview Questions
Q1 β Valid Parentheses
π§ How to Approach This
Step 1 β Recognize the pattern: Opening β push; closing β must match stack top.
Step 2 β Brute force (what NOT to do): Count opens vs closes β fails for
"([)]".Step 3 β Optimal insight: Stack enforces LIFO matching order.
Step 4 β Algorithm:
1. Map closing β expected opening
2. Push opens; on close: top must match, else false
3. Stack must be empty at end
Step 5 β Edge cases: Only closings; interleaved
{[()]}.
function isValid(s) {
const stack = [];
const map = { ')': '(', ']': '[', '}': '{' };
for (const ch of s) {
if ('([{'.includes(ch)) stack.push(ch);
else {
if (!stack.length || stack.at(-1) !== map[ch]) return false;
stack.pop();
}
}
return stack.length === 0;
}
Time: O(n) | Space: O(n)
Q2 β Min Stack
π§ How to Approach This
Step 1 β Recognize the pattern: Track running minimum with each pushed value.
Step 2 β Brute force (what NOT to do): Scan stack for min β O(n) per call.
Step 3 β Optimal insight: Store [val, currentMin] pairs β O(1) all ops.
Step 4 β Algorithm:
1. Push: append [val, Math.min(val, top_min)]
2. Pop/top/getMin: index top pair
Step 5 β Edge cases: First push; duplicate min values.
class MinStack {
constructor() { this.stack = []; }
push(val) {
const min = this.stack.length ? Math.min(val, this.stack.at(-1)[1]) : val;
this.stack.push([val, min]);
}
pop() { this.stack.pop(); }
top() { return this.stack.at(-1)[0]; }
getMin() { return this.stack.at(-1)[1]; }
}
Time: O(1) all ops | Space: O(n)
Q3 β Evaluate Reverse Polish Notation
π§ How to Approach This
Step 1 β Recognize the pattern: Operand β push; operator β pop two, compute, push result.
Step 2 β Brute force (what NOT to do): Build expression tree β overkill.
Step 3 β Optimal insight: Stack evaluates RPN left to right directly.
Step 4 β Algorithm:
1. For each token: if number β push; if operator β pop b, pop a, compute a OP b, push
2. Return stack[0]
Step 5 β Edge cases: Division truncates toward zero β use
Math.trunc.
function evalRPN(tokens) {
const stack = [];
for (const t of tokens) {
if ('+-*/'.includes(t)) {
const b = stack.pop(), a = stack.pop();
if (t === '+') stack.push(a + b);
else if (t === '-') stack.push(a - b);
else if (t === '*') stack.push(a * b);
else stack.push(Math.trunc(a / b)); // truncate toward zero
} else {
stack.push(Number(t));
}
}
return stack[0];
}
Time: O(n) | Space: O(n)
Q4 β Daily Temperatures
π§ How to Approach This
Step 1 β Recognize the pattern: "Days until warmer" β monotonic decreasing stack.
Step 2 β Brute force (what NOT to do): Nested loops β O(nΒ²).
Step 3 β Optimal insight: Stack of indices. When warmer found, result = i - popped_i.
Step 4 β Algorithm:
1. Stack of indices (decreasing temps)
2. Pop when top's temp < current; result[pop] = i - pop
3. Push i
Step 5 β Edge cases: No warmer day β stays 0.
function dailyTemperatures(temps) {
const n = temps.length;
const result = new Array(n).fill(0);
const stack = [];
for (let i = 0; i < n; i++) {
while (stack.length && temps[stack.at(-1)] < temps[i]) {
const idx = stack.pop();
result[idx] = i - idx;
}
stack.push(i);
}
return result;
}
Time: O(n) | Space: O(n)
Q5 β Next Greater Element I
π§ How to Approach This
Step 1 β Recognize the pattern: Precompute next-greater for nums2 β map; answer queries for nums1.
Step 2 β Brute force (what NOT to do): For each nums1 element scan nums2 β O(mΒ·n).
Step 3 β Optimal insight: Monotonic stack on nums2, fill map. Then O(1) lookup per query.
Step 4 β Algorithm:
1. Build monotonic stack on nums2, fill map[val] = next_greater
2. For each num in nums1: answer = map.get(num) ?? -1
Step 5 β Edge cases: No greater element β -1.
function nextGreaterElement(nums1, nums2) {
const map = new Map();
const stack = [];
for (const num of nums2) {
while (stack.length && stack.at(-1) < num) map.set(stack.pop(), num);
stack.push(num);
}
return nums1.map(n => map.get(n) ?? -1);
}
Time: O(n + m) | Space: O(n)
Q6 β Next Greater Element II (Circular Array)
π§ How to Approach This
Step 1 β Recognize the pattern: Circular β process array twice (0..2n-1), index % n.
Step 2 β Brute force (what NOT to do): Nested loop with modulo β O(nΒ²).
Step 3 β Optimal insight: Single loop 0..2n-1. Only push on first pass.
Step 4 β Algorithm:
1. Monotonic decreasing stack of indices
2. Loop 0..2n-1; idx = i % n; resolve stack when nums[top] < nums[idx]
3. Push only when i < n
Step 5 β Edge cases: All same values β all -1.
function nextGreaterElements(nums) {
const n = nums.length;
const result = new Array(n).fill(-1);
const stack = [];
for (let i = 0; i < 2 * n; i++) {
const idx = i % n;
while (stack.length && nums[stack.at(-1)] < nums[idx]) {
result[stack.pop()] = nums[idx];
}
if (i < n) stack.push(idx);
}
return result;
}
Time: O(n) | Space: O(n)
Q7 β Largest Rectangle in Histogram
π§ How to Approach This
Step 1 β Recognize the pattern: Monotonic increasing stack β pop when shorter bar arrives.
Step 2 β Brute force (what NOT to do): All bar pairs β O(nΒ²).
Step 3 β Optimal insight: Each popped bar defines a rectangle; width = i - new_top - 1.
Step 4 β Algorithm:
1. Append sentinel 0, stack starts with [-1]
2. Pop when top's height > current; compute area
Step 5 β Edge cases: Sentinel forces all bars to be processed.
function largestRectangleArea(heights) {
heights = [...heights, 0];
const stack = [-1];
let maxArea = 0;
for (let i = 0; i < heights.length; i++) {
while (stack.at(-1) !== -1 && heights[stack.at(-1)] > heights[i]) {
const h = heights[stack.pop()];
const w = i - stack.at(-1) - 1;
maxArea = Math.max(maxArea, h * w);
}
stack.push(i);
}
return maxArea;
}
Time: O(n) | Space: O(n)
Q8 β Trapping Rain Water (Stack)
π§ How to Approach This
Step 1 β Recognize the pattern: Stack-based horizontal layer water calculation.
Step 2 β Brute force (what NOT to do): For each bar find max left/right β O(nΒ²).
Step 3 β Optimal insight: Decreasing stack. When taller bar arrives, pop and compute valley water.
Step 4 β Algorithm:
1. Monotonic decreasing stack of indices
2. When heights[i] > heights[top]: pop, compute water = (min(left, right) - bottom) Γ width
Step 5 β Edge cases: No valley (sorted) β 0.
function trap(height) {
const stack = [];
let water = 0;
for (let i = 0; i < height.length; i++) {
while (stack.length && height[stack.at(-1)] < height[i]) {
const bottom = stack.pop();
if (!stack.length) break;
const left = stack.at(-1);
const h = Math.min(height[left], height[i]) - height[bottom];
const w = i - left - 1;
water += h * w;
}
stack.push(i);
}
return water;
}
Time: O(n) | Space: O(n)
Q9 β Implement Queue Using Two Stacks
π§ How to Approach This
Step 1 β Recognize the pattern: Two LIFO stacks β FIFO queue via lazy transfer.
Step 2 β Brute force (what NOT to do): Transfer on every operation β O(n) each.
Step 3 β Optimal insight: Only transfer when outStack is empty β amortized O(1).
Step 4 β Algorithm:
1. push β inStack
2. pop/peek: if outStack empty, drain inStack
Step 5 β Edge cases: Multiple pops without pushes.
class MyQueue {
constructor() { this.inStack = []; this.outStack = []; }
push(x) { this.inStack.push(x); }
pop() { this._transfer(); return this.outStack.pop(); }
peek() { this._transfer(); return this.outStack.at(-1); }
empty() { return !this.inStack.length && !this.outStack.length; }
_transfer() {
if (!this.outStack.length)
while (this.inStack.length) this.outStack.push(this.inStack.pop());
}
}
Time: O(1) amortized | Space: O(n)
Q10 β Implement Stack Using Two Queues
π§ How to Approach This
Step 1 β Recognize the pattern: Two FIFO queues β LIFO by rotating elements.
Step 2 β Brute force (what NOT to do): Copy on every operation.
Step 3 β Optimal insight: Push to q2, drain q1 into q2, swap. Front of q1 is always the top.
Step 4 β Algorithm:
1. push: enqueue to q2, drain q1βq2, swap q1βq2
2. pop/top: dequeue/peek front of q1
Step 5 β Edge cases: Single element; multiple pops.
class MyStack {
constructor() { this.q1 = []; this.q2 = []; }
push(x) {
this.q2.push(x);
while (this.q1.length) this.q2.push(this.q1.shift());
[this.q1, this.q2] = [this.q2, this.q1];
}
pop() { return this.q1.shift(); }
top() { return this.q1[0]; }
empty() { return this.q1.length === 0; }
}
Time: O(n) push, O(1) pop | Space: O(n)
Q11 β Sliding Window Maximum (Deque)
π§ How to Approach This
Step 1 β Recognize the pattern: Monotonic decreasing deque of indices.
Step 2 β Brute force (what NOT to do): Scan k elements per window β O(nΒ·k).
Step 3 β Optimal insight: Front = current max. Remove expired from front; smaller from back.
Step 4 β Algorithm:
1. Remove expired from front (index < i - k + 1)
2. Remove smaller from back
3. Record result when i >= k-1
Step 5 β Edge cases: k = 1 (trivial); k > n.
function maxSlidingWindow(nums, k) {
const deque = [], result = [];
for (let i = 0; i < nums.length; i++) {
while (deque.length && deque[0] < i - k + 1) deque.shift();
while (deque.length && nums[deque.at(-1)] < nums[i]) deque.pop();
deque.push(i);
if (i >= k - 1) result.push(nums[deque[0]]);
}
return result;
}
Time: O(n) | Space: O(k)
Q12 β Basic Calculator (+ - and Parentheses)
π§ How to Approach This
Step 1 β Recognize the pattern: Stack saves (result, sign) on
(; restores on).Step 2 β Brute force (what NOT to do): Expression tree β complex.
Step 3 β Optimal insight: Track result and sign; push/pop state on parentheses.
Step 4 β Algorithm:
1. result = 0, sign = 1
2. On
(: push [result, sign]; reset3. On
): combine with popped stateStep 5 β Edge cases: Multi-digit numbers; spaces.
function calculate(s) {
const stack = [];
let result = 0, sign = 1, num = 0;
for (const ch of s) {
if (ch >= '0' && ch <= '9') {
num = num * 10 + Number(ch);
} else if (ch === '+' || ch === '-') {
result += sign * num; num = 0;
sign = ch === '+' ? 1 : -1;
} else if (ch === '(') {
stack.push(result, sign); result = 0; sign = 1;
} else if (ch === ')') {
result += sign * num; num = 0;
result *= stack.pop(); // sign before paren
result += stack.pop(); // result before paren
}
}
return result + sign * num;
}
Time: O(n) | Space: O(n)
Q13 β Basic Calculator II (+ - * /)
π§ How to Approach This
Step 1 β Recognize the pattern: Push +/- operands; apply */Γ· immediately.
Step 2 β Brute force (what NOT to do): Two-pass for precedence levels.
Step 3 β Optimal insight: Stack for addition terms; * / modify stack top.
Step 4 β Algorithm:
1. Track num and lastSign
2. On operator/end: push or modify stack based on lastSign
Step 5 β Edge cases: Multi-digit numbers; trailing digit.
function calculateII(s) {
const stack = [];
let num = 0, sign = '+';
s = s.trim();
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (ch >= '0' && ch <= '9') num = num * 10 + Number(ch);
if ((ch < '0' || ch === ' ' ? ch !== ' ' : false) || i === s.length - 1) {
// trigger: non-digit non-space, or last char
}
if (('+-*/'.includes(ch) && ch !== ' ') || i === s.length - 1) {
if (sign === '+') stack.push(num);
else if (sign === '-') stack.push(-num);
else if (sign === '*') stack.push(stack.pop() * num);
else stack.push(Math.trunc(stack.pop() / num));
sign = ch; num = 0;
}
}
return stack.reduce((a, b) => a + b, 0);
}
Time: O(n) | Space: O(n)
Q14 β Decode String
π§ How to Approach This
Step 1 β Recognize the pattern: Nested encoding β stack saves state on
[.Step 2 β Brute force (what NOT to do): Recursion without stack β deep nesting risk.
Step 3 β Optimal insight: Push [currentStr, k] on
[; on]expand and prepend saved string.Step 4 β Algorithm:
1. Build current string and number
2. On
[: push state, reset3. On
]: pop, expand cur Γ k, prepend savedStep 5 β Edge cases: Multi-digit numbers; nested
3[a2[c]].
function decodeString(s) {
const stack = [];
let cur = '', k = 0;
for (const ch of s) {
if (ch >= '0' && ch <= '9') {
k = k * 10 + Number(ch);
} else if (ch === '[') {
stack.push([cur, k]); cur = ''; k = 0;
} else if (ch === ']') {
const [prev, times] = stack.pop();
cur = prev + cur.repeat(times);
} else {
cur += ch;
}
}
return cur;
}
Time: O(output length) | Space: O(n)
Q15 β Remove Duplicate Letters
π§ How to Approach This
Step 1 β Recognize the pattern: Greedy + monotonic stack. Remove larger chars if they appear later.
Step 2 β Brute force (what NOT to do): All permutations β factorial.
Step 3 β Optimal insight: Track last occurrence. Pop stack top if larger and has future occurrence.
Step 4 β Algorithm:
1. Compute last occurrence index of each char
2. Monotonic increasing stack with "in-stack" Set
3. Pop when top > curr and top appears later
Step 5 β Edge cases: Duplicate chars; single occurrences.
function removeDuplicateLetters(s) {
const last = {};
for (let i = 0; i < s.length; i++) last[s[i]] = i;
const stack = [], inStack = new Set();
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (inStack.has(ch)) continue;
while (stack.length && stack.at(-1) > ch && last[stack.at(-1)] > i) {
inStack.delete(stack.pop());
}
stack.push(ch); inStack.add(ch);
}
return stack.join('');
}
Time: O(n) | Space: O(1) (26 letters)
Q16 β Remove K Digits
π§ How to Approach This
Step 1 β Recognize the pattern: Greedy β remove digit when smaller one follows (k times).
Step 2 β Brute force (what NOT to do): All combinations β O(n choose k).
Step 3 β Optimal insight: Monotonic increasing stack; pop when top > curr and k > 0.
Step 4 β Algorithm:
1. Increasing stack, pop when top > curr and k > 0
2. If k > 0, remove from end
3. Trim leading zeros
Step 5 β Edge cases: k = n (return "0"); leading zeros after removal.
function removeKdigits(num, k) {
const stack = [];
for (const d of num) {
while (k > 0 && stack.length && stack.at(-1) > d) { stack.pop(); k--; }
stack.push(d);
}
while (k-- > 0) stack.pop();
const result = stack.join('').replace(/^0+/, '');
return result || '0';
}
Time: O(n) | Space: O(n)
Q17 β Score of Parentheses
π§ How to Approach This
Step 1 β Recognize the pattern: Stack-based scoring β
()= 1, nested = 2 Γ inner.Step 2 β Brute force (what NOT to do): Recursive counting β harder to track.
Step 3 β Optimal insight: Stack with 0 as layer marker; on
)merge or score.Step 4 β Algorithm:
1. On
(: push 02. On
): pop v; if 0 β push 1 (leaf), else push 2*v; add to new topStep 5 β Edge cases: Adjacent
()()β summed at same level.
function scoreOfParentheses(s) {
const stack = [0];
for (const ch of s) {
if (ch === '(') stack.push(0);
else {
const v = stack.pop();
stack[stack.length - 1] += Math.max(2 * v, 1);
}
}
return stack[0];
}
Time: O(n) | Space: O(n)
Q18 β Asteroid Collision
π§ How to Approach This
Step 1 β Recognize the pattern: Collision only when positive (right) meets negative (left).
Step 2 β Brute force (what NOT to do): Simulate all pairs β O(nΒ²).
Step 3 β Optimal insight: Stack. Positive β push. Negative β collide with positive tops.
Step 4 β Algorithm:
1. Positive or stack top is negative β push
2. If negative: while top > 0 and |neg| > top: pop
3. Equal β pop both; if negative wins β push it
Step 5 β Edge cases: All same direction; equal collision (both destroyed).
function asteroidCollision(asteroids) {
const stack = [];
for (const a of asteroids) {
let alive = true;
while (alive && a < 0 && stack.length && stack.at(-1) > 0) {
if (stack.at(-1) < -a) stack.pop();
else if (stack.at(-1) === -a) { stack.pop(); alive = false; }
else alive = false;
}
if (alive) stack.push(a);
}
return stack;
}
Time: O(n) | Space: O(n)
Q19 β Car Fleet
π§ How to Approach This
Step 1 β Recognize the pattern: Sort descending by position; cars that don't catch up form new fleets.
Step 2 β Brute force (what NOT to do): Simulate time steps β imprecise.
Step 3 β Optimal insight: Time = (target - pos) / speed. If next car's time β€ fleet top's time β same fleet.
Step 4 β Algorithm:
1. Sort by position descending
2. Compute arrival time; if time > stack top β new fleet (push), else merge
Step 5 β Edge cases: Single car; equal positions.
function carFleet(target, position, speed) {
const cars = position.map((p, i) => [p, speed[i]]);
cars.sort((a, b) => b[0] - a[0]);
const stack = [];
for (const [pos, spd] of cars) {
const time = (target - pos) / spd;
if (!stack.length || time > stack.at(-1)) stack.push(time);
}
return stack.length;
}
Time: O(n log n) | Space: O(n)
Q20 β Exclusive Time of Functions
π§ How to Approach This
Step 1 β Recognize the pattern: Stack of running functions; compute elapsed on each log event.
Step 2 β Brute force (what NOT to do): Simulate per time unit.
Step 3 β Optimal insight: Track prevTime. On each event, attribute delta to current top.
Step 4 β Algorithm:
1. Stack of function IDs
2. start: add delta to top (if any), push id, update prev
3. end: add delta+1 to top, pop, prev = timestamp+1
Step 5 β Edge cases: Nested calls; single function.
function exclusiveTime(n, logs) {
const result = new Array(n).fill(0);
const stack = [];
let prev = 0;
for (const log of logs) {
const [id, type, time] = log.split(':');
const t = Number(time);
if (type === 'start') {
if (stack.length) result[stack.at(-1)] += t - prev;
stack.push(Number(id)); prev = t;
} else {
result[stack.pop()] += t - prev + 1;
prev = t + 1;
}
}
return result;
}
Time: O(n) | Space: O(n)
Q21 β Number of Visible People in Queue
π§ How to Approach This
Step 1 β Recognize the pattern: Monotonic decreasing stack, right to left.
Step 2 β Brute force (what NOT to do): For each person scan right β O(nΒ²).
Step 3 β Optimal insight: Process right-to-left. Stack holds heights decreasingly. Count popped (visible shorter) + 1 if stack still has taller.
Step 4 β Algorithm:
1. Traverse right to left
2. Count shorter people popped; add 1 if stack non-empty (taller blocker visible)
Step 5 β Edge cases: Strictly decreasing heights.
function canSeePersonsCount(heights) {
const n = heights.length;
const result = new Array(n).fill(0);
const stack = [];
for (let i = n - 1; i >= 0; i--) {
let count = 0;
while (stack.length && stack.at(-1) < heights[i]) { stack.pop(); count++; }
if (stack.length) count++;
result[i] = count;
stack.push(heights[i]);
}
return result;
}
Time: O(n) | Space: O(n)
Q22 β Design Circular Queue
π§ How to Approach This
Step 1 β Recognize the pattern: Fixed-size ring buffer with head/tail pointer arithmetic.
Step 2 β Brute force (what NOT to do): Array shift/unshift β O(n).
Step 3 β Optimal insight: Circular index:
(head + size) % capacity.Step 4 β Algorithm:
1. Array of size k, head = 0, size = 0
2. Enqueue: write at (head+size)%k; dequeue: advance head
Step 5 β Edge cases: Full on enqueue, empty on dequeue.
class MyCircularQueue {
constructor(k) { this.data = new Array(k); this.cap = k; this.head = 0; this.size = 0; }
enQueue(val) {
if (this.isFull()) return false;
this.data[(this.head + this.size) % this.cap] = val; this.size++; return true;
}
deQueue() {
if (this.isEmpty()) return false;
this.head = (this.head + 1) % this.cap; this.size--; return true;
}
Front() { return this.isEmpty() ? -1 : this.data[this.head]; }
Rear() { return this.isEmpty() ? -1 : this.data[(this.head + this.size - 1) % this.cap]; }
isEmpty() { return this.size === 0; }
isFull() { return this.size === this.cap; }
}
Time: O(1) all ops | Space: O(k)
Q23 β Design Circular Deque
π§ How to Approach This
Step 1 β Recognize the pattern: Ring buffer with front/back insert/delete.
Step 2 β Brute force (what NOT to do): Array unshift β O(n) front ops.
Step 3 β Optimal insight: insertFront: head = (head-1+cap)%cap.
Step 4 β Algorithm:
1. insertFront: move head back circularly
2. insertLast: write at (head+size)%cap
3. delete: adjust head or size
Step 5 β Edge cases: Full/empty checks before every op.
class MyCircularDeque {
constructor(k) { this.data = new Array(k); this.cap = k; this.head = 0; this.size = 0; }
insertFront(v) {
if (this.isFull()) return false;
this.head = (this.head - 1 + this.cap) % this.cap;
this.data[this.head] = v; this.size++; return true;
}
insertLast(v) {
if (this.isFull()) return false;
this.data[(this.head + this.size) % this.cap] = v; this.size++; return true;
}
deleteFront() {
if (this.isEmpty()) return false;
this.head = (this.head + 1) % this.cap; this.size--; return true;
}
deleteLast() {
if (this.isEmpty()) return false;
this.size--; return true;
}
getFront() { return this.isEmpty() ? -1 : this.data[this.head]; }
getRear() { return this.isEmpty() ? -1 : this.data[(this.head + this.size - 1) % this.cap]; }
isEmpty() { return this.size === 0; }
isFull() { return this.size === this.cap; }
}
Time: O(1) all ops | Space: O(k)
Q24 β Design Hit Counter
π§ How to Approach This
Step 1 β Recognize the pattern: Sliding window of 300s β queue of timestamps.
Step 2 β Brute force (what NOT to do): Store all hits, filter on getHits β O(n) per call.
Step 3 β Optimal insight: Queue; on getHits remove entries older than timestamp - 299.
Step 4 β Algorithm:
1. hit: push timestamp
2. getHits(t): while front <= t-300: shift; return length
Step 5 β Edge cases: Multiple hits at same timestamp; empty window.
class HitCounter {
constructor() { this.queue = []; }
hit(timestamp) { this.queue.push(timestamp); }
getHits(timestamp) {
while (this.queue.length && this.queue[0] <= timestamp - 300) this.queue.shift();
return this.queue.length;
}
}
Time: O(n) worst per getHits, O(1) amortized | Space: O(n)
Q25 β Longest Valid Parentheses
π§ How to Approach This
Step 1 β Recognize the pattern: Stack tracks unmatched indices; length = i - stack_top.
Step 2 β Brute force (what NOT to do): Check every substring β O(nΒ²) or O(nΒ³).
Step 3 β Optimal insight: Stack initialized with [-1].
(β push index;)β pop, compute length, or push new base.Step 4 β Algorithm:
1. Stack = [-1] (base)
2.
(β push i;)β pop; if empty push i as new base; else max = i - stack.topStep 5 β Edge cases: All openings; all closings; alternating.
function longestValidParentheses(s) {
const stack = [-1];
let max = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i);
} else {
stack.pop();
if (!stack.length) stack.push(i); // new base
else max = Math.max(max, i - stack.at(-1));
}
}
return max;
}
Time: O(n) | Space: O(n)