Stack Problems

Six classic interview problems — Valid Parentheses, Min Stack, Evaluate Reverse Polish Notation, Daily Temperatures, Car Fleet and Largest Rectangle in Histogram — solved from zero with one tool: a pile where you only ever touch the top. For each problem you will see the slow obvious idea, why a stack fixes it, the pseudocode, the Dart code, and an animation you can feed your own input, with the input and the stack drawn side by side. By the end you will recognise the three stack patterns (matching pairs, evaluating, and the monotonic stack) and be able to explain why a loop that pops inside a loop is still O(n).

What a stack is and the three stack patterns

A pile of plates in a canteen. A clean plate is always put on top of the pile, and the next person always takes the plate from the top. Nobody pulls a plate out of the middle. So the plate that was put down last is the plate that leaves first. That rule — last in, first out — is the whole idea of a stack.

Some words first:

Dart's List is already a stack

Dart has no separate Stack class, and it does not need one. A growable List where you use the end of the list as the top is a perfect stack:

Stack wordDartCostOn an empty stack
push xstack.add(x)O(1) amortizedfine
popstack.removeLast() (returns the item)O(1)throws a RangeError
peekstack.lastO(1)throws a StateError ("No element")
is it empty?stack.isEmpty / stack.isNotEmptyO(1)—

Why the end and not the front? The items of a List sit side by side in memory. Adding or removing at the end moves nothing else. Adding or removing at the front (insert(0, x), removeAt(0)) shifts every other item by one place — O(n) each time. "Amortized O(1)" for add means: once in a while the list runs out of room and copies itself into a bigger block, but because the block doubles, the copying averages out to a constant amount per add. That is explained with pictures in D26 · List methods (growth and amortised O(1)); stacks built from scratch are in C10 · Stacks and queues.

The three stack patterns

Almost every stack interview question is one of these three shapes. Learn to spot them and the code nearly writes itself.

  1. Matching pairs. Something opens, something later closes it, and the most recent thing that opened must be the first to close — brackets, HTML tags, function calls. Push when something opens; pop and compare when something closes. (Valid Parentheses.)
  2. Evaluating and undoing. Keep results you are not ready to use yet. In Reverse Polish Notation, numbers wait on the stack until an operator needs the last two. In an editor, every action is pushed so that "undo" pops the most recent one. (Evaluate RPN, Min Stack.)
  3. The monotonic stack. "For every item, find the next item to its right that is bigger (or smaller)." Keep a stack of items that are still waiting for their answer. Their values only go one way from bottom to top — that is what monotonic means (always falling, or always rising). When a new item arrives, it is the answer for every waiting item it beats, so those are popped. (Daily Temperatures, Car Fleet, Largest Rectangle.)

Here is the monotonic stack on its own — "next greater element": for each number, the first bigger number to its right, or −1 if there is none.

Why a loop that pops inside a loop is still O(n)

The code above has a while inside a for, which usually smells like O(n²). Count the stack operations instead of the loops. Each index is pushed exactly once (the last line of the for). Once an index is popped it is gone for good, so it is popped at most once. The while loop can only pop what was pushed, so over the whole run all the while loops together do at most n pops — no matter how they are spread out. One step may pop five items and the next step none. In total: at most 2n stack operations, O(n). This way of counting the total instead of the worst single step is called amortized analysis — the same argument as the multipop example in C17 · Amortized Analysis. The push and pop counters in the animation show it live.

You have met this idea before: Sliding Window Maximum in Step 13.3 · Sliding Window Maximum kept a monotonic deque — the same "pop the items that can never win again" rule. A deque was needed there because old items also expired from the front. Here nothing expires, so a stack (one open end) is enough.

Ask three questions: (1) Does the most recent unfinished thing have to be dealt with first? → matching-pairs stack. (2) Do I need to hold results until something later uses them? → evaluation stack. (3) Does every item need "the next bigger / smaller item" to one side? → monotonic stack, with each item pushed once and popped once.
Popping an empty stack. removeLast() on an empty list throws a RangeError and last throws a StateError. In every while that pops, check stack.isNotEmpty first — Dart's && stops at the first false, so stack.isNotEmpty && nums[stack.last] < x never reads last of an empty list.

Valid Parentheses

The task. You get a string s made only of the six characters ( ) [ ] { }. Return true if the brackets are correctly matched: every opener is closed by the same kind of closer, the closers come in the reverse order of their openers (inner pairs close before outer ones), and no closer appears without an opener before it. Otherwise return false. The empty string counts as valid.

Example 1
Input: s = "[{}](())"
Output: true
Why: "{}" closes inside "[ ]", then "()" closes inside "( )"; every pair is the same kind.
Example 2
Input: s = "{[}]"
Output: false
Why: "}" arrives while "[" is the most recent opener — the pairs cross each other.
Example 3
Input: s = ")("
Output: false
Why: the first ")" has nothing to close, even though the counts of each kind are equal.
Constraints
Speed you need: deleting pairs again and again is O(n²) = 108 character copies → O(n) time with a stack, O(n) extra space.
Russian nesting dolls. You open a big doll, then a smaller one inside it, then a smaller one inside that. To put them back together you must close the smallest, most recently opened doll first. Trying to close the big doll while a small one is still open simply does not work. A stack remembers exactly which doll you opened last.

Brute force. Find any adjacent matching pair — (), [] or {} — and delete it. Repeat until nothing changes. The string was valid exactly when nothing is left. Each round scans the whole string, and there can be n/2 rounds: O(n²).

Key insight. When a closer arrives, it must match the most recent opener that is still open. "Most recent still open" is exactly the top of a stack of openers. So: push every opener; on a closer, pop the top and check that it is the right kind. Counting is not enough — ")(" and "{[}]" have balanced counts but are invalid.

Why it is correct. The stack always holds the openers that are still open, in the order they were opened, with the newest on top. A closer is legal only if it closes the newest one, and popping removes exactly that opener. At the end, anything still on the stack was opened and never closed.

An edge case: every closer matches, but one opener is never closed. The loop finishes without any mismatch, and only the final check stack is empty catches it.

Complexity. Time O(n) — each character is pushed at most once and popped at most once. Space O(n) — in the worst case (all openers, like "((((") every character waits on the stack.

Common mistakes. (1) Returning true at the end instead of stack.isEmpty: "(()" would pass (question p04-q10 shows this bug). (2) Popping without checking for an empty stack first: ")" crashes with a RangeError instead of returning false. (3) Using three counters, one per kind: they cannot see the order, so "{[}]" would pass. (4) A quick win: if the length is odd, the answer is false immediately.

Follow-ups interviewers ask. Only one kind of bracket: a counter is enough (the stack's height is all that matters) — the fewest insertions to fix such a string (p04-q4), the deepest nesting (p04-q11), the fewest deletions (p04-q19) and the longest valid piece (p04-q21). Brackets mixed with other text: ignore every other character. HTML-like tags: push the tag name, compare names on a closing tag.

The idea underneath: stacks built from scratch in C10 · Stacks and queues, using the List methods of D26 · List methods.

Min Stack

The task. Design a stack of whole numbers with four operations, each in O(1) time: push(x) puts x on top, pop() removes the top item, top() returns the top item, and getMin() returns the smallest item currently in the stack. You may assume pop, top and getMin are only called when the stack is not empty.

Example 1
Input: push 4, push 1, push 6, getMin, pop, pop, getMin, top
Output: 1, 4, 4
Why: the minimum is 1 while 1 is inside; after 6 and 1 are popped only 4 is left.
Example 2
Input: push -3, push -3, pop, getMin
Output: -3
Why: two copies of the minimum; removing one leaves the other.
Example 3
Input: push 9, top, getMin
Output: 9, 9
Why: with one item, it is both the top and the minimum.
Constraints
Speed you need: scanning on every getMin is O(n) each, up to 9·108 steps → O(1) per operation, O(n) extra space.
A pile of boxes in a warehouse, each with a sticky note saying "the lightest box from here down weighs …". When you put a new box on top you write its note by comparing just two numbers: the new box's weight and the note on the box right under it. When you take the top box away, the note on the box now on top is still true — it was written when that box was the top. Nobody ever has to weigh the whole pile again.

Brute force. A plain stack, and getMin looks at every item. Push, pop and top are O(1), but getMin is O(n).

Key insight. The minimum of a stack can only change in two ways: a push can lower it, and a pop can restore an older minimum. A pop never creates a brand new minimum — it can only go back to one that existed before. So remember, for every level of the stack, the minimum of everything at or below that level. Keep a second stack mins next to values: when x is pushed, push min(x, top of mins) onto mins. Pop both stacks together. Then getMin is just the top of mins.

Why it is correct. The invariant (a fact that is true after every operation): mins[i] is the smallest of values[0..i]. A push makes it true for the new top because the smallest of "everything below plus x" is min(x, mins[i − 1]). A pop removes the top level only, and every lower mins entry was about values that are all still there. The same two-stack idea can store pairs instead: one stack of (value, minSoFar) records.

An edge case: the same minimum pushed several times. Because mins gets an entry on every push, each copy has its own partner, and popping one copy keeps the minimum correct.

Complexity. Time O(1) for every operation (one or two list operations at the end). Space O(n) — the second stack doubles the memory.

Common mistakes. (1) Keeping a single variable min: after popping the minimum you have no idea what the previous minimum was. (2) The space-saving variant that pushes onto mins only when x < top of mins: with duplicates (push 3, push 3, pop) it pops the only 3 from mins and loses the minimum — you need ≤, and pops must check equality. Pushing on every push, as above, avoids the trap. (3) Calling getMin on an empty stack: the task promises it never happens, but real code should check isEmpty first.

Follow-ups interviewers ask. O(1) extra memory: store a coded value 2x − min whenever the minimum changes (p04-q30). A max stack (flip the comparison). A queue with O(1) getMin: build it from two min stacks, the same way p04-q2 builds a queue from two stacks. Undo/redo history (p04-q12).

The idea underneath: a stack carrying extra facts per level — the same "remember the answer so far" idea as the running minimum in Step 13.3 · Best Time to Buy and Sell Stock.

Evaluate Reverse Polish Notation

The task. In Reverse Polish Notation (RPN, also called postfix) the operator comes after its two operands: 3 4 + means 3 + 4, and 3 4 + 2 * means (3 + 4) × 2. No brackets are ever needed. You get the expression as a list of tokens (each token is a whole number such as "12" or "-7", or one of "+" "-" "*" "/"). Return its value. Division between two whole numbers truncates toward zero (drop the fractional part: 7 / 2 = 3 and −7 / 2 = −3). The expression is always well-formed and never divides by zero.

Example 1
Input: tokens = ["3", "4", "+", "2", "*"]
Output: 14
Why: (3 + 4) × 2.
Example 2
Input: tokens = ["20", "-7", "/", "5", "+"]
Output: 3
Why: 20 / −7 = −2.857…, truncated toward zero to −2; then −2 + 5 = 3.
Example 3
Input: tokens = ["8"]
Output: 8
Why: a single number is already the value.
Constraints
Speed you need: rescanning for the next operator after every step is O(n²) = 108 → O(n) time with a stack, O(n) extra space.
A cook who reads a recipe card left to right. Ingredients (numbers) go onto the counter, the newest in front. Whenever the card says an action ("mix", "add"), the cook grabs the two most recent things on the counter, does the action, and puts the result back in front. The result is now an ingredient for later actions. At the end, exactly one dish is left on the counter.

Brute force. Find the first operator in the list. The two tokens just before it must be its operands. Replace those three tokens with the result, and start scanning again from the beginning. Each step is O(n) and there are up to n/2 steps: O(n²).

Key insight. In postfix, an operator always uses the two values produced most recently — and "most recent" means a stack. Push numbers. On an operator, pop twice, compute, push the result. The first pop is the right operand b (it was pushed last) and the second pop is the left operand a; then compute a op b. For + and × the order does not matter, but for − and / it does: "5 9 -" is 5 − 9 = −4, not 9 − 5.

Division in Dart — exactly. The integer division operator ~/ truncates toward zero: it computes the exact quotient and drops the fractional part. So -7 ~/ 2 is -3 and 7 ~/ -2 is -3, which is exactly what this problem wants. (Careful: unary minus binds tighter than ~/, so -7 ~/ 2 means (-7) ~/ 2.) This is different from floor division, which rounds down: Python's -7 // 2 is −4, and Dart's (-7 / 2).floor() is also −4. And Dart's % is never negative (-7 % 2 is 1), while remainder keeps the sign ((-7).remainder(2) is −1). Dividing an int by zero with ~/ throws an IntegerDivisionByZeroException.

An edge case: a negative number divided by a positive one, where truncating and rounding down give different answers.

Complexity. Time O(n) — one push or two pops plus a push per token. Space O(n) — in an expression like 1 2 3 4 + + + all the numbers wait on the stack before any operator comes.

Common mistakes. (1) Swapping the operands: popping a first gives 9 − 5 instead of 5 − 9. (2) Using / instead of ~/: in Dart / always returns a double (7 / 2 is 3.5), which does not even fit back into a List<int>. (3) Using floor division ((a / b).floor()) — wrong for negative quotients. (4) Treating "-7" as the operator -: check for the exact one-character tokens "+" "-" "*" "/", and parse everything else with int.parse, which accepts a leading minus.

Follow-ups interviewers ask. Evaluate a normal (infix) expression with brackets: one stack of saved partial results is enough for + and − (p04-q20); add × and ÷ with their higher priority (p04-q27). Convert infix to postfix: the "shunting-yard" algorithm keeps a stack of operators waiting for their operands. Decode nested repeat patterns like 3[a2[c]] (p04-q15), the same "save the outer state, work on the inner one" idea.

The idea underneath: the call stack of D07 · Functions & the Call Stack works the same way — a function's arguments are pushed, the function uses the most recent ones, and its result is handed back to whoever is waiting.

Daily Temperatures

The task. temps[i] is the temperature on day i. For every day, return how many days you must wait after it until a strictly warmer day. If no warmer day ever comes, return 0 for that day.

Example 1
Input: temps = [70, 71, 69, 68, 72, 70]
Output: [1, 3, 2, 1, 0, 0]
Why: day 1 (71) waits until day 4 (72): 3 days. Days 4 and 5 never see anything warmer.
Example 2
Input: temps = [50, 60, 70]
Output: [1, 1, 0]
Why: it gets warmer every day, so each day waits exactly one day.
Example 3
Input: temps = [65]
Output: [0]
Why: one day has no future at all.
Constraints
Speed you need: walking forward from every day is O(n²) = 1010 when temperatures keep falling → O(n) time with a monotonic stack, O(n) extra space.
People waiting at a bus stop for a taller person to come and stand behind them (silly, but it works). Each newcomer looks at the line of waiting people from the most recent backwards. Everyone shorter than the newcomer is finally done waiting — they note the time and leave. As soon as the newcomer meets someone at least as tall, the rest of the line is even taller (or equal) and keeps waiting, so the newcomer stops looking and joins the line. The line of waiting people always gets shorter from the oldest to the newest.

Brute force. For each day, walk forward until a warmer day. If temperatures keep falling, every walk goes to the end: O(n²).

Key insight. This is "next greater element" with a twist: we want the distance, so the stack stores day indexes, not temperatures. Keep a stack of days that are still waiting for a warmer day. Their temperatures never rise from bottom to top (a monotonic decreasing stack) — because if a warmer day had come after a waiting day, that waiting day would already have been popped. When day day arrives, pop every waiting day that is colder: for each one, today is its first warmer day, so it waited day − colder days. Then push today.

Why popping is safe. A popped day has its final answer — the first warmer day is the earliest one, and nothing later can beat "today". And a day that stops the popping (warmer or equal) is also not answered by anything we skipped: every day below it on the stack is at least as warm, so today cannot be warmer than them either.

An edge case: temperatures only fall. Nothing is ever popped, every day waits on the stack until the end, and every answer stays 0.

Complexity. Time O(n) — every day is pushed once and popped at most once (the counting argument from the opening section). Space O(n) — in a falling week every day is on the stack at the same time. If temperatures only rise, the stack never holds more than one day.

Common mistakes. (1) Storing temperatures on the stack: then you cannot compute how many days passed — store the index and read temps[index]. (2) Popping on ≤ instead of <: an equal day is not warmer, so it must not answer the waiting day. (3) Forgetting that days still on the stack at the end must answer 0 — fill the result with 0 at the start, as the code does. (4) Using a stack that is increasing by mistake (popping warmer days): you would answer "next colder day" instead.

Follow-ups interviewers ask. The "next bigger value" itself instead of the distance (p04-q7). The list is circular — after the last day comes the first again (p04-q13: loop twice). Look backwards instead: how many days in a row up to today were not warmer than today — the stock span (p04-q8). Temperatures only between 30 and 100: you can also scan from the right and keep, for each temperature, the next day it appears — O(n · 71).

The idea underneath: the monotonic stack of the opening section, and the same pop-what-can-never-win rule as the deque in Step 13.3 · Sliding Window Maximum.

Car Fleet

The task. n cars drive along a single-lane road toward the same target mile. Car i starts at mile position[i] (all different, all before the target) and drives at speed[i] miles per hour. A car can never pass the car in front of it: if it catches up, it slows down and drives right behind it at the slower speed. Cars driving together like that form a fleet. A car that catches up exactly at the target also joins that fleet. A single car is a fleet too. Return how many fleets arrive at the target.

Example 1
Input: target = 20, position = [2, 8, 12, 16, 5], speed = [3, 2, 4, 1, 1]
Output: 3
Why: alone, the cars need 6, 6, 2, 4 and 15 hours. The car at 12 (2 h) catches the car at 16 (4 h). The car at 8 (6 h) cannot catch them, and the car at 2 (6 h) catches the car at 5 (15 h). Fleets: {16, 12}, {8}, {5, 2}.
Example 2
Input: target = 10, position = [0, 4, 8], speed = [5, 3, 1]
Output: 1
Why: all three need exactly 2 hours, so they all meet at the target at the same moment.
Example 3
Input: target = 9, position = [3], speed = [2]
Output: 1
Why: one car is one fleet.
Constraints
Speed you need: checking every car against every car ahead is O(n²) = 1010 → O(n log n) time (the sort dominates) with a stack, O(n) extra space.
Think about arrival times, not speeds. Picture each car's "alone" arrival time written on its roof. Stand at the target and look back down the road. The car nearest to you sets the pace for anything stuck behind it. A car further back whose roof number is smaller or equal would arrive sooner on its own — impossible without passing — so it must catch up and arrive together with the car ahead. A car with a bigger roof number arrives later no matter what: it starts a new fleet, and now it sets the pace for the cars behind it.

Brute force. A car starts its own fleet exactly when it needs more time than every car ahead of it (otherwise it catches up with whichever fleet arrives last among those ahead). Check every car against every car ahead: O(n²).

Key insight. Sort the cars by position, closest to the target first. Walk through them in that order, keeping a stack of fleet arrival times; the top is the fleet just ahead of the current car. Compute the car's alone time (target − position) ÷ speed. If it is bigger than the top, the car can never catch that fleet: push its time — a new fleet. Otherwise it catches the fleet ahead at or before the target and arrives with it: push nothing. The answer is the stack's size. The stored times only rise from bottom to top — another monotonic stack.

Why only the top matters. The fleet just ahead is the slowest to arrive of everything ahead (every fleet further ahead arrives sooner — that is why they are separate). If the car cannot catch the fleet right in front of it, it cannot catch any fleet beyond it either.

An edge case: every car needs exactly the same time. Each car "catches up" exactly at the target, which counts as joining, so the stack holds a single fleet.

Complexity. Time O(n log n) for the sort; the stack pass is O(n). Space O(n) for the order and the stack. In this problem the stack only ever grows, so a single variable "slowest arrival so far" would do — the stack makes the fleets visible and carries over to harder versions.

Common mistakes. (1) Sorting closest-to-the-start first: a car's fate depends on the cars ahead of it, so you must process those first. (2) Using ≥ to start a new fleet: equal times meet exactly at the target, which counts as one fleet. (3) Integer division for the time: in Dart (target - position[car]) ~/ speed[car] drops the fraction, so 7 / 2 and 6 / 2 would both look like 3 hours — use / (a double). (4) Worrying that doubles are rounded: here they are safe. Equal fractions such as 6 / 2 and 3 / 1 give exactly the same double, and two different times differ by at least 1 / (s1 · s2), far more than a double's rounding at these sizes. If an interviewer asks for whole numbers only, compare d1 · s2 > d2 · s1 instead (the products fit easily in Dart's 64-bit int):

Follow-ups interviewers ask. When does each car catch the car ahead (collision times)? Scan from the front with a stack of cars that are still "free", popping cars that will crash into someone else before they can be caught. Rocks moving left and right that destroy each other on contact (p04-q14) — the same "what does the newcomer do to the top of the stack?" loop.

The idea underneath: sorting first (C02 · Insertion & Merge Sort, and Dart's sort with a compare function from D26 · List methods), then a monotonic stack.

Largest Rectangle in Histogram

The task. A histogram is a row of bars of width 1 standing side by side; heights[i] is the height of bar i. Return the area of the largest rectangle that fits completely inside the bars. The rectangle must sit on the ground and cover a run of neighbouring bars, so its height is limited by the shortest bar it covers.

Example 1
Input: heights = [3, 1, 4, 5, 4, 2]
Output: 12
Why: bars 2 to 4 (heights 4, 5, 4) hold a rectangle of height 4 and width 3.
Example 2
Input: heights = [2, 4]
Output: 4
Why: 2 × 2 (both bars) and 4 × 1 (the tall bar alone) tie at 4.
Example 3
Input: heights = [1, 1, 1, 1, 7]
Output: 7
Why: the wide flat rectangle gives 1 × 5 = 5, but the single tall bar gives 7.
Constraints
Speed you need: every pair of ends is O(n²) = 1010 → O(n) time with a monotonic increasing stack, O(n) extra space.
Every bar asks: "if the rectangle's height were exactly my height, how wide could it get?" It can spread left and right until it bumps into a bar that is lower than itself. So each bar needs to know its nearest lower bar on the left and on the right — two "next smaller element" questions. A stack answers both in one pass: a bar learns its right wall at the moment it is popped (the newcomer that is lower), and its left wall is simply the bar sitting under it on the stack.

Brute force. Try every run of bars i..j, keeping the running minimum height while j grows: O(n²).

Key insight. Keep a stack of bar indexes whose heights rise from bottom to top (a monotonic increasing stack). A bar on the stack is still "growing to the right" — nothing lower has come yet. When bar i arrives and is lower than the top bar, the top bar's rectangle cannot reach i: pop it and measure it now.

The width, carefully. When bar t (height tall) is popped at step i:

The sentinel. Bars still on the stack when the list ends never met a lower bar on their right. Instead of writing a second loop for them, pretend there is one extra bar of height 0 at index n. It is lower than every real bar (or equal to a bar of height 0, whose area is 0 anyway), so it pops everything that is left, with i = n as their right wall. A made-up item like this, added only to remove a special case, is called a sentinel.

An edge case: all bars the same height. Nothing pops until the sentinel arrives; then the bars are popped from the right, and each one measures a wider rectangle than the last: widths 1, 2, 3, 4.

Complexity. Time O(n) — every index (and the sentinel) is pushed once and popped at most once. Space O(n) — when the heights rise all the way, the whole list sits on the stack.

Common mistakes. (1) Width i − t instead of i − left − 1: it forgets that the rectangle also stretches left of t, over taller bars that were popped earlier. (2) Forgetting the bars left on the stack at the end — the sentinel (or a cleanup loop) is required; without it, [1, 2, 3] would return 0. (3) Using left = 0 for an empty stack instead of −1: the width would be one too small. (4) Equal heights: popping on > (as here) or ≥ are both correct — with ≥, an earlier equal bar is measured too short, but the last bar of the equal run is popped later with the full width.

Follow-ups interviewers ask. The largest rectangle of 1s in a grid of 0s and 1s: build a histogram for each row and run this function on it (p04-q22). The sum of the minimum of every subarray — each value counts once for every subarray where it is the smallest, which needs exactly the left and right walls computed here (p04-q23). Water trapped between bars, solved layer by layer with a stack (p04-q25).

The idea underneath: two "next smaller element" answers from one monotonic stack (the opening section), and the two-pointer view of the same bars in Step 13.2 · Trapping Rain Water and Container With Most Water.

Quiz

Interview questions

Variations and follow-ups of the six problems above — the questions interviewers move to once you have solved the classic version. Every solution is tested in Dart on its examples and on hundreds of random inputs against a slow reference.

Cheat sheet

ProblemPatternWhat the stack holdsTimeSpaceThe move and why it is safe
Valid ParenthesesMatching pairsopeners still openO(n)O(n)A closer must match the most recent open opener → pop and compare; empty at the end
Min StackEvaluating / historyvalues + the minimum at each levelO(1) eachO(n)A pop only restores an older minimum, which is already stored underneath
Evaluate RPNEvaluatingnumbers waiting for an operatorO(n)O(n)Operator → pop b, pop a, push a op b; Dart's ~/ truncates toward zero
Daily TemperaturesMonotonic, decreasingday indexes still waitingO(n)O(n)Warmer day pops every colder waiting day: answer = day − colder
Car FleetMonotonic, rising timesarrival time of each fleetO(n log n)O(n)Sort closest first; time > top → new fleet, else it joins the fleet ahead
Largest RectangleMonotonic, increasingbar indexes, heights risingO(n)O(n)Lower bar pops taller ones: width = i − left − 1; a height-0 sentinel empties the stack
TemplateShapeUse when
Dart stackfinal s = <T>[]; push s.add(x); pop s.removeLast(); peek s.last; guard with s.isNotEmptyalways — the end of the list is the top
Next greater (to the right)for i: while top value < a[i]: pop j, answer[j] = a[i]; push i"next warmer / bigger / taller", stock span (look left: pop ≤, read the top)
Next smaller (to the right)same loop with > instead of <histogram walls, sum of subarray minimums
Save and restoreon "(" or "[": push the outer state, start fresh; on ")" or "]": pop and combinecalculators, decode 3[a2[c]], nested scores
Two stacksinbox + outbox (queue), done + undone (undo/redo), values + mins (min stack)design questions with O(1) amortized operations

Each index is pushed once and popped at most once, so a monotonic stack is O(n) even with a while inside the for. If items must also leave from the old end (a window that slides), use a deque instead (Step 13.3 · Sliding Window Maximum).