🚀 Try This Problem First!
Before reading the solution, attempt it yourself on LeetCode — you'll retain the concept far better.
🔗 Problem Link: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
Understanding the Problem
You are given an array prices where prices[i] is the stock price on day i. You can buy on one day and sell on a later day. You want to maximize your profit.
Goal: Return the maximum profit possible. If no profit can be made, return 0.
Key Rules:
- You must buy before you sell — no going back in time.
- You can only make one transaction (one buy, one sell).
- If prices only go down, profit is impossible — return 0.
Constraints:
- 1 ≤ prices.length ≤ 10⁵
- 0 ≤ prices[i] ≤ 10⁴
Understanding the Problem With a Real-World Analogy
Imagine you're watching a stock ticker for a week. You want to pick the single best day to buy and a later day to sell. You can't predict the future — you just have historical prices. The question is: looking back at all the prices, what was the best single buy-sell pair you could have chosen?
Key Observation (Before Writing a Single Line of Code)
The profit on any given pair of days is:
profit = prices[sell_day] - prices[buy_day]
To maximize profit, for every potential sell day, we want the lowest possible buy price seen so far to the left of it. This is the core insight that drives the efficient solution.
If at any point the current price is lower than our tracked minimum, there is no point keeping the old minimum — the new one is strictly better for any future sell day.
Intuition — The Two Pointer Approach
We use two pointers i (buy pointer) and j (sell pointer), both starting at the beginning with i = 0 and j = 1.
At every step we ask: is prices[j] greater than prices[i]?
If yes → this is a profitable pair. Compute the profit and update the maximum if it's better.
If no → prices[j] is cheaper than prices[i]. There's no reason to keep i where it is — any future sell day would be better served by buying at j instead. So we move i to j.
Either way, j always moves forward by 1 until it reaches the end of the array.
Why Moving i to j is Correct
This is the most important conceptual point. When prices[j] < prices[i], you might wonder — why not just skip j and move on? Why move i?
Because for any future day k > j, the profit buying at j is:
prices[k] - prices[j]
And the profit buying at i is:
prices[k] - prices[i]
Since prices[j] < prices[i], buying at j will always give a higher or equal profit for the same sell day k. So we update i = j — there is no scenario where keeping the old i is better.
Dry Run — Example 1 (Step by Step)
Input: prices = [7, 1, 5, 3, 6, 4]
We start with i = 0, j = 1, maxP = 0.
Step 1: i = 0, j = 1 → prices[i] = 7, prices[j] = 1
7 > 1 → prices[j] is cheaper. Move i to j. i = 1, j moves to 2. maxP = 0.
Step 2: i = 1, j = 2 → prices[i] = 1, prices[j] = 5
1 < 5 → Profitable! profit = 5 - 1 = 4. 4 > 0 → update maxP = 4. j moves to 3.
Step 3: i = 1, j = 3 → prices[i] = 1, prices[j] = 3
1 < 3 → Profitable! profit = 3 - 1 = 2. 2 < 4 → maxP stays 4. j moves to 4.
Step 4: i = 1, j = 4 → prices[i] = 1, prices[j] = 6
1 < 6 → Profitable! profit = 6 - 1 = 5. 5 > 4 → update maxP = 5. j moves to 5.
Step 5: i = 1, j = 5 → prices[i] = 1, prices[j] = 4
1 < 4 → Profitable! profit = 4 - 1 = 3. 3 < 5 → maxP stays 5. j moves to 6. j = 6 = prices.length → loop ends.
Output: maxP = 5 ✅ (Buy at day 2 price=1, sell at day 5 price=6)
Dry Run — Example 2 (Step by Step)
Input: prices = [7, 6, 4, 3, 1]
We start with i = 0, j = 1, maxP = 0.
Step 1: i = 0, j = 1 → prices[i] = 7, prices[j] = 6
7 > 6 → Move i to j. i = 1, j moves to 2. maxP = 0.
Step 2: i = 1, j = 2 → prices[i] = 6, prices[j] = 4
6 > 4 → Move i to j. i = 2, j moves to 3. maxP = 0.
Step 3: i = 2, j = 3 → prices[i] = 4, prices[j] = 3
4 > 3 → Move i to j. i = 3, j moves to 4. maxP = 0.
Step 4: i = 3, j = 4 → prices[i] = 3, prices[j] = 1
3 > 1 → Move i to j. i = 4, j moves to 5. j = 5 = prices.length → loop ends.
Output: maxP = 0 ✅ (Prices only went down, no profitable transaction exists)
The Code — Implementation
Code Walkthrough — Step by Step
Initialization: i = 0 acts as our buy day pointer (always pointing to the lowest price seen so far). j = 1 is our sell day pointer scanning forward. maxP = 0 is our answer — defaulting to 0 handles the case where no profit is possible.
The loop condition: i < j ensures we never sell before buying. j < prices.length ensures we don't go out of bounds.
When prices[i] > prices[j]: The current sell day price is cheaper than our buy day. We update i = j, because buying at j dominates buying at i for all future sell days.
When prices[j] >= prices[i]: We have a potential profit. We compute it and update maxP if it beats the current best.
j always increments: Regardless of which branch we take, j++ moves the sell pointer forward every iteration — this is what drives the loop to completion.
Common Mistakes to Avoid
Not returning 0 for no-profit cases: If prices are strictly decreasing, maxP never gets updated and stays 0. The initialization maxP = 0 handles this correctly — never initialize it to a negative number.
Using a nested loop (brute force): A common first instinct is two nested loops checking every pair. That is O(N²) and will TLE on large inputs. The two pointer approach solves it in O(N).
Thinking you need to sort: Sorting destroys the order of days, which is fundamental to the problem. Never sort the prices array here.
Moving i forward by 1 instead of to j: When prices[j] < prices[i], some people write i++ instead of i = j. This is wrong — you might miss the new minimum entirely and waste iterations.
Complexity Analysis
Time Complexity: O(N) The j pointer traverses the array exactly once from index 1 to the end. The i pointer only moves forward and never exceeds j. So the entire algorithm is a single linear pass — O(N).
Space Complexity: O(1) No extra arrays, maps, or stacks are used. Only three integer variables — i, j, and maxP.
Alternative Way to Think About It (Min Tracking)
Some people write this problem using a minPrice variable instead of two pointers. Both approaches are equivalent — the two pointer framing is slightly more intuitive visually, but here is the min-tracking version for reference:
The logic is the same — always track the cheapest buy day seen so far, and for each day compute what profit would look like if you sold today.
Similar Problems (Build on This Foundation)
- LeetCode 122 — Best Time to Buy and Sell Stock II (multiple transactions allowed) [ Blog is also avaliable on this - Read Now ]
- LeetCode 123 — Best Time to Buy and Sell Stock III (at most 2 transactions) [ Blog is also avaliable on this - Read Now ]
- LeetCode 188 — Best Time to Buy and Sell Stock IV (at most k transactions)
- LeetCode 309 — Best Time to Buy and Sell Stock with Cooldown
- LeetCode 714 — Best Time to Buy and Sell Stock with Transaction Fee
LeetCode 121 is the foundation. Master this one deeply before moving to the others — they all build on the same core idea.
Key Takeaways
✅ For every potential sell day, the best buy day is always the minimum price seen to its left — this drives the whole approach.
✅ When the sell pointer finds a cheaper price than the buy pointer, always move the buy pointer there — it strictly dominates the old buy day for all future sells.
✅ Initialize maxP = 0 so that no-profit scenarios (prices only going down) are handled automatically.
✅ The two pointer approach solves this in a single linear pass — O(N) time and O(1) space.
✅ j always increments every iteration — i only moves when a cheaper price is found.
Happy Coding! If this clicked for you, the entire Stock series on LeetCode will feel much more approachable.




