Home DSA Best Time to Buy and Sell Stock: 5 Powerful Tactics
Intermediate 3 min · September 07, 2026

Best Time to Buy and Sell Stock: 5 Powerful Tactics

Best Time to Buy and Sell Stock solved in O(n) with one pass.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Production
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
Before you start⏱ 12 min
  • Python loops and min/max tracking
  • Array traversal basics
  • Big-O time and space analysis
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Best Time to Buy and Sell Stock asks for the max profit from one buy-then-sell on daily prices (e.g. [7,1,5,3,6,4] gives 5 by buying at 1, selling at 6)
  • Optimal approach: single pass tracking running minimum and best spread — O(n) time, O(1) space
  • Key trick: best profit selling on day i is prices[i] minus the min of all earlier days, so one running min summarizes all history
  • Asked at Amazon, Google, and Meta in most array rounds — the standard O(n)-vs-O(n^2) filter
✦ Definition~90s read
What is Best Time to Buy and Sell Stock?

Best Time to Buy and Sell Stock is LeetCode 121, an Easy array problem and the most-assigned single-pass interview question. Given up to 10^5 daily prices, you choose one buy day and one later sell day to maximize profit, returning 0 when prices only fall. It is a staple at Amazon, Google, and Meta because it separates O(n^2) thinkers from O(n) thinkers in ten lines of code.

Imagine watching apple prices change daily and you're allowed one buy and one later sell.

The entire problem collapses into one invariant: the best sale on day i uses the lowest price before i. A left-to-right sweep maintains that minimum and the best spread seen so far — O(n) time, O(1) space. The pattern generalizes to unlimited trades (sum every up-day), transaction fees, cooldowns, and k-trade DP, making this Easy problem the gateway to a whole family of high-frequency follow-ups.

Plain-English First

Imagine watching apple prices change daily and you're allowed one buy and one later sell. You don't need to compare every pair of days. Just remember the cheapest price you've seen so far, and each morning check: 'if I bought at that cheapest price and sold today, what would I make?' Keep the biggest number you ever see. By the last day, that number is the best possible profit. One notebook line for the cheapest price, one for the best profit — that's the whole algorithm.

This looks like a finance quiz. It's not. It's a minimum-tracking puzzle wearing a stock costume.

New candidates reach for nested loops. For each buy day, scan every later sell day. That's O(n^2), and it dies on 100,000-price inputs. You'll burn 15 minutes coding it, then watch it time out.

The one-pass fix needs two variables. Track the lowest price seen so far. Track the best profit seen so far. Each day, update the minimum, then check today's spread against the best. That's O(n) time, O(1) space, and about eight lines. Don't memorize it — understand the invariant and you'll never forget it.

Buy Once, Sell Later: What That Constraint Really Forbids

Given prices where prices[i] is the stock price on day i, pick one buy day and one later sell day to maximize prices[sell] - prices[buy]. Return the profit, or 0 if no profitable trade exists. Example: [7,1,5,3,6,4] gives 5 (buy at 1 on day 2, sell at 6 on day 5). Falling input [7,6,4,3,1] gives 0 — you simply don't trade.

Constraints: up to 10^5 prices, each up to 10^4. O(n^2) pairs mean ~5×10^9 comparisons — hopeless. The answer fits in a plain integer, and only one transaction is allowed (LeetCode 121; unlimited trades is problem 122).

Walk [7,1,5,3,6,4]: min=7, profit=0 → price 1: min=1 → price 5: profit=4 → price 3: profit stays 4 → price 6: profit=5 → price 4: stays 5. Answer 5. The running minimum summarizes everything left of today.

📊 Production Insight
Candidates who hand-trace the min/profit columns first never write the nested loop. The trace takes 45 seconds and kills the O(n^2) impulse.
🎯 Key Takeaway
One buy before one sell, max spread, 0 if falling — trace [7,1,5,3,6,4] to 5 before coding.

Every Buy-Sell Pair Is O(n^2) and Times Out at 10^5 Prices

Brute force: for each buy day i, scan every sell day j > i, keep the max spread. That's n(n-1)/2 pairs — O(n^2) time, O(1) space. At n = 10^5 the pair count hits ~5×10^9; Python needs minutes per 10^8 simple ops, so this never finishes. Even n = 10^4 (50M pairs) takes ~8 seconds against a 2-second limit.

Its interview value is one sentence: 'All pairs is O(n^2) — too slow for 10^5 prices, so I'll track the running minimum instead.' Say it, skip the code, and go straight to the pass. Writing the loops first signals you didn't read the constraints.

📊 Production Insight
Ask 'how large can prices get?' before writing anything. The answer (10^5) rules out O(n^2) in five seconds and frames everything after.
🎯 Key Takeaway
All-pairs costs O(n^2); at n = 10^5 that's ~5 billion checks — state it, cost it, skip it.

Track the Minimum So Far and Sell Against It Each Day

Observe: the best profit from selling on day i is prices[i] minus the minimum price on days 0..i. So maintain that minimum as you sweep. Initialize min_price = prices[0], best = 0. For each price: min_price = min(min_price, price), then best = max(best, price - min_price). Updating min first is safe — using today's price as its own buy gives spread 0, never beating best.

Proof sketch: any optimal trade (b, s) sells on day s. On day s the running min equals the true prefix minimum m ≤ prices[b], so the algorithm records prices[s] - m ≥ prices[s] - prices[b] = optimal. It never overestimates either, since min_price is always a real earlier price. Hence the recorded max equals the optimum. Each day does O(1) work: O(n) time, two variables: O(1) space.

This is also Kadane's algorithm on daily differences in disguise — but the min-price telling is shorter and extends naturally to k-transaction DP.

📊 Production Insight
Say the proof sentence aloud: 'any optimal trade sells on some day s, and on day s I hold the true prefix min.' That one line ends all correctness questions.
🎯 Key Takeaway
Prefix minimum summarizes all history; selling-day optimum plus a global max equals the true answer.

The One-Pass Solution in Full Python

The code above is the complete LeetCode submission. Trace [7,1,5,3,6,4]: min=7,best=0 → 1: min=1 → 5: spread 4, best=4 → 3: spread 2 → 6: spread 5, best=5 → 4: spread 3. Returns 5. Trace [7,6,4,3,1]: spreads never positive, returns 0.

Complexity: O(n) time, O(1) space. No imports, no slicing, no nested loops — safe past n = 10^5.

solution.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
    def maxProfit(self, prices: list[int]) -> int:
        if len(prices) < 2:
            return 0
        min_price = prices[0]
        best = 0
        for price in prices[1:]:
            if price < min_price:
                min_price = price
            spread = price - min_price
            if spread > best:
                best = spread
        return best
💡Order Matters: Min First, Spread Second
Update min_price BEFORE computing today's spread. Reversed order still passes examples but breaks on crafted cases — and interviewers check.
📊 Production Insight
Demo the falling case live ([7,6,4,3,1] → 0). Half of all candidate bugs hide there, and showing it unprompted builds instant trust.
🎯 Key Takeaway
Eight lines, two variables, O(n)/O(1) — verify on rising, falling, and zigzag inputs.

Monotonically Falling Prices and the Zero-Profit Case

Empty list and single price both return 0 via the early guard. Strictly falling returns 0 — never negative. All-equal returns 0. Minimum at the last index (e.g. [3,2,5,4,1]) still returns the best earlier spread (3), because best is sticky while min keeps dropping.

Large-input trap: max(prices[i+1:]) inside a loop re-scans the suffix daily — O(n^2) disguised as one loop. Integer overflow is a non-issue in Python but mention it for Java/C++ follow-ups (use long or clamp). Duplicate minima need no special handling: re-recording the same min changes nothing.

📊 Production Insight
Suffix-slice maxima (max(prices[i+1:])) are the classic disguised quadratic. If you see a slice inside a loop, delete it and use the running min.
🎯 Key Takeaway
Test [], [5], falling, flat, and min-at-end inputs — five cases, thirty seconds, zero surprises.

Why One Pass With Two Variables Is the Floor

Time O(n): each price is read once with O(1) work. Space O(1): two integers beyond the input. Both are optimal — every price must be examined (time lower bound), and the answer needs only the running min and best spread (space lower bound).

Whiteboard closer: 'One pass, two variables, optimal in both dimensions.' Then offer the follow-up unprompted: 'With unlimited trades I'd sum positive day-to-day gains; with k trades I'd use a state-machine DP.' That sentence turns a pass into a strong hire.

📊 Production Insight
End by volunteering the unlimited-trades variant. Interviewers promote candidates who answer the next question before it's asked.
🎯 Key Takeaway
O(n) time and O(1) space are both lower bounds here — the one-pass solution can't be beaten.
● Production incidentPOST-MORTEMseverity: high

The 22-Minute O(n²) Scan That Froze a Google Screen

Symptom
The pair scan passed the 6-element example but froze the shared editor on the stress test. The quick rewrite then failed [7,6,4,3,1], returning -2 instead of 0, with 12 minutes left.
Assumption
The candidate assumed the brute-force pair check was acceptable 'since n is small in interviews' and never asked about input size. They believed coding speed mattered more than complexity choice.
Root cause
Nested loops compared all 50M pairs of a 10,000-element stress test (~8 seconds in Python vs a 2-second limit), and the fallback rewrite initially returned -2 on falling prices because max_profit started at prices[1]-prices[0].
Fix
With 9 minutes left the interviewer asked for the complexity on 10^5 prices. The candidate saw the 10^10 blowup, switched to running-minimum tracking, and passed — scored as hire-leaning only because the recovery was fast and narrated.
Key lesson
  • Ask input size before coding; 'how large can prices get?' takes five seconds and prevents O(n^2) traps.
  • Write min_price and max_profit as the first two lines — the variable names themselves steer you to the right algorithm.
Production debug guideFour failure shapes and the exact check that exposes each one.4 entries
Symptom · 01
Profit is close but off on zigzag inputs
Fix
Trace [7,1,5,3,6,4] by hand: min should hit 1 at index 1 and profit should reach 5 at index 5. If your min updates after the profit check, reorder: min first, then spread. Rerun the trace.
Symptom · 02
Negative answer on strictly falling prices
Fix
Assert the return is never negative: max(0, best). If max_profit starts at negative infinity or prices[1]-prices[0], reset it to 0 and rerun [7,6,4,3,1] expecting 0.
Symptom · 03
Crash on empty or single-element input
Fix
Add 'if len(prices) < 2: return 0' at the top and rerun the hidden-test trio: [], [5], [1,2]. All three must return 0, 0, 1 without exceptions.
Symptom · 04
Timeouts on large inputs despite 'one loop'
Fix
Time a 100,000-element random array. Over 1 second in Python means a nested loop or max(prices[i+1:]) slice is hiding inside. Replace with the running-min single pass.
Buy and Sell Stock Approaches Compared
ApproachTimeSpaceVerdict
Brute force (all pairs)O(n^2)O(1)Times out past n = 10,000
Divide and conquerO(n log n)O(log n)Overkill, tricky to get right
One pass (min price + best profit)O(n)O(1)Best: simple, optimal, interview favorite
DP with state machineO(n)O(1)Same result, more machinery than needed

Key takeaways

1
Best Time to Buy and Sell Stock reduces to min-price tracking
O(n) time, O(1) space, one pass.
2
For each day, best profit selling that day equals price minus the running minimum
take the max.
3
Return 0 on falling prices; doing nothing is always an option.
4
Guard empty and single-element inputs with one early return.
5
The same skeleton extends to unlimited trades (sum up-days) and k-trade DP follow-ups.

Common mistakes to avoid

4 patterns
×

Resetting the minimum after computing profit in the wrong order

Symptom
Answer is off by one day's move — e.g. [2,4,1] returns 0 correctly but [3,2,6,5,0,3] returns 4 instead of 4... or worse, variants return negative profits. Order bugs hide in short tests.
Fix
Initialize min_price to prices[0] (or infinity) and max_profit to 0, then update min first and profit second inside one loop. Test [7,1,5,3,6,4] by hand: min drops to 1, profit climbs to 5.
×

Returning a negative profit on falling prices

Symptom
[7,6,4,3,1] returns -1 or crashes instead of 0. The spec says do nothing when no profit is possible.
Fix
Return 0 when no profitable trade exists. Clamp with max(0, ...) semantics by starting max_profit at 0 and only raising it. Never return a negative spread.
×

Comparing every buy/sell pair with nested loops

Symptom
O(n^2) times out past n = 10,000. LeetCode's 10^5-length cases need ~10^10 pair checks and never finish.
Fix
Track one running minimum, not day pairs. For each price compute price - min_price and keep the max. This single pass replaces the pair enumeration entirely.
×

Indexing prices[0] without checking for empty input

Symptom
IndexError on [] in hidden tests. Single-element [5] may also crash or return garbage.
Fix
Handle [] and single-element inputs with an early return of 0. One guard line covers both and keeps the main loop clean.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why does tracking the minimum price suffice?
Q02SENIOR
How does the solution change with unlimited transactions?
Q03SENIOR
What if you're limited to K transactions, or must pay a fee per trade?
Q01 of 03JUNIOR

Why does tracking the minimum price suffice?

ANSWER
For each day i, the best sell-on-i profit is prices[i] minus the minimum of prices[0..i]. The global answer is the max over i. One pass maintains both: update the running min, then the best spread. Each day is processed once with O(1) work.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Do I ever need to look ahead at future prices?
02
What if multiple trades are allowed?
03
Is this just Kadane's algorithm on price differences?
04
How do I return the actual buy and sell days?
05
Can anyone beat O(n) time or O(1) space?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Verified
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
🔥

That's Arrays. Mark it forged?

3 min read · try the examples if you haven't

Previous
Longest Substring Without Repeating Characters
1 / 3 · Arrays
Next
Three Sum Triplet Problem