Online Stock Span
Each day a price arrives; return its span — how many consecutive days ending today had price ≤ today's.
Open on LeetCode ↗Intuition
A price swallows the spans of every smaller-or-equal price before it. Keep a stack of (price, span) pairs with strictly decreasing prices: a new price pops and absorbs what it dominates, then pushes its accumulated span.
Same monotonic stack, but streaming — you can't look ahead, so instead of resolving pending indices you absorb the ones you dominate. Storing (price, span) pairs lets a popped entry hand over its accumulated count, so spans compress instead of being recounted. That's the trick for any online "how far back does my run extend?" question.
Approach
Spans compress history
Once a day is inside some later day's span, it can never matter individually again — safe to merge and discard.
Pop while ≤
New price p starts with span 1, adds the span of every popped pair with price ≤ p, then pushes (p, span).
Amortized O(1) per day
Each pair pushed once, popped at most once — n calls cost O(n) total.
Solution & live demo
Common pitfalls
Discarding the popped element's span
while self.stack and self.stack[-1][0] <= price:
self.stack.pop()
span += 1while self.stack and self.stack[-1][0] <= price:
span += self.stack.pop()[1]A popped entry stands for a whole run of earlier days it already absorbed, not a single day. Counting it as 1 undercounts every span after the first compression — the accumulated total is exactly why the amortised cost stays O(1).
Using < and mishandling equal prices
while self.stack and self.stack[-1][0] < price:
while self.stack and self.stack[-1][0] <= price:
The span counts days with price less than or equal to today, so an equal earlier price is part of the run and must be absorbed. Strict < leaves it on the stack and reports a span one short.
Edge cases
Every new price pops everything — spans grow like 1,2,3…
≤ comparison absorbs equals into the new span (span counts ≤ days).