Largest Rectangle in Histogram

Every bar wants to be the shortest bar in a rectangle. The monotonic stack waits until a shorter bar arrives — then it finally knows exactly how far that rectangle could stretch.

The memory hook

When a shorter bar arrives, cash out.

HardMonotonic stackO(n) timeO(n) space
01

Why the stack works

intuition

Scan left to right and keep indices whose heights are increasing. While the new bar is at least as tall as the stack top, the older bars can still stretch right. The instant a shorter bar appears, those taller bars are finished — so measure them and move on.

1WaitPush an index while heights go uphill. Its rectangle might still grow.
2DiscoverA shorter bar is the right boundary for every taller bar behind it.
3MeasureAfter popping, the new stack top is the left boundary. Width falls out.
The one sentence to remember: a bar is popped only when its maximal rectangle can no longer grow right — so each rectangle is calculated exactly once, at the exact moment its full width is known.
02

Watch the stack discover rectangles

playable trace

Start with the classic [2, 1, 5, 6, 2, 3]. Step slowly, then replay at speed once the invariant clicks.

Playable trace · monotonic stack
Frames are generated from the same logic as the code below.
Presets
Current event
Ready — press Play
Best area so far
0
stack · indices
The stack begins empty. We scan left → right and append a sentinel 0 at the end so every remaining bar gets popped.
unread current waiting in stack popped / measured best rectangle
0 / 0
03

Short code, explicit boundaries

the solution
Python · monotonic stack
def largestRectangleArea(heights):
    stack = []       # increasing heights' indices
    best = 0

    # sentinel 0 flushes the stack at the end
    for i, h in enumerate(heights + [0]):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            left = stack[-1] if stack else -1
            width = i - left - 1
            best = max(best, height * width)
        stack.append(i)

    return best

The sentinel index is pushed after the final flush. It is only a control value; it is not part of the histogram.

Right boundaryiThe first index with a smaller height — the bar that triggered the pop.
Left boundarystack[-1] + 1Just after the next smaller bar on the left (the new stack top).
Widthi - stack[-1] - 1Fallout of storing indices, not just heights.
Why does this never miss a rectangle? Every rectangle has a shortest bar. That bar is popped exactly when the scan reaches the first smaller height on its right; the stack top after popping gives its first smaller height on the left.
TimeO(n)each index enters and leaves the stack once
SpaceO(n)the stack can hold up to n indices
04

Two quick mental checks

make it stick
[2, 1, 5, 6, 2, 3]
Best = 10

When the second 2 arrives, pop 6 (width 1 → 6), then pop 5 (width 2 → 10). The bar of height 5 stretches across indices 2–3.

[2, 4]
Best = 4

The sentinel 0 arrives after the scan: pop 4 with width 1, then pop 2 with width 2. The final flush is not an edge-case patch — it is the same right-boundary logic.

What to say out loud
“I maintain indices of increasing heights. When I see a smaller height, each popped bar has found its first smaller element on the right. The remaining stack top is its first smaller element on the left, so I know its exact maximal width. Each index is pushed and popped once, giving linear time.”