Best Time to Buy and Sell Stock in Java DSA: Brute Force to the One-Pass Scan
-
Last Updated: July 24, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Best Time to Buy and Sell Stock in Java DSA explained for beginners. Brute force to one-pass scan with dry runs, clean code, and complexity comparison.
Best Time to Buy and Sell Stock in Java is the second problem in this series. It follows Two Sum, and it teaches a different habit. Here you stop comparing pairs and start remembering one useful fact.
The story is simple. You get the daily price of a stock. You may buy on one day and sell on a later day. Your job is to find the largest profit you can make.
You get one trade only. Buy once, sell once, and you are done. Also, the sell day must come after the buy day, so time only moves forward.
We build the answer in three steps. Brute force checks every buy and sell pair. A cleaner version tracks the best price behind us. The final one-pass scan does the whole job in a single sweep.
Every approach here comes with a full dry run. We trace the same six prices through each one, so you can watch fifteen comparisons shrink down to six easy steps.
Let us fix the rules before we write any code. A muddy problem statement is where most bugs are born.
For our prices the answer is 5. You buy on day 1 at price 1, then sell on day 4 at price 6. That gives 6 minus 1, which is 5.
Notice we return the profit, not the two days. Beginners often return the indices out of habit from Two Sum. Read the required output carefully.
The lowest price in our array is 1 and the highest is 7. Yet the answer is not 7 minus 1.
That one rule shapes every solution below. Any approach you write has to respect the direction of time.
Our six prices are chosen to be a little awkward. That makes the traces honest.
A tidy rising array like [1, 2, 3] hides all of this. A bumpy one shows you what the loop really does.
| 💡 Interview Insight Interviewers love to ask what happens when prices only fall, such as [7, 6, 4, 3, 1]. The answer there is 0, because you simply do not trade. Saying that out loud early shows you thought about edge cases before coding. |
Two small ideas carry this whole problem. Both show up again in later array questions, so it pays to learn them properly.
Say you buy at 1 and sell at 6. Your profit is 6 minus 1, which is 5. That is the entire formula.
profit = sellPrice - buyPrice buy = 1, sell = 6 -> profit = 5 buy = 7, sell = 4 -> profit = -3 (a loss) buy = 3, sell = 3 -> profit = 0 (no gain)
Look at the second line. Selling lower than you bought gives a negative number. We never accept those, because doing nothing always beats losing money.
So our answer starts at 0 and only ever moves up. A negative profit gets ignored, which is exactly what “do not trade” means.
Here is the second idea, and it is the one that makes this problem click.
Walk through the prices from left to right. At every day, ask one question. What is the cheapest price I have seen so far?
That value has a name. We call it the running minimum, and it never grows. It either stays the same or drops.
Why does that help? Because the cheapest price so far is always a legal buy day. It sits behind you, so selling today is allowed.
We track one more value alongside the minimum. It holds the best profit found so far.
This one works the opposite way. It never shrinks, and it only climbs when today beats the old record.
maxProfit = max(maxProfit, todayPrice - minPrice) old best = 4, today gives 5 -> new best = 5 old best = 5, today gives 2 -> best stays 5
Keeping a running best is a habit you will use constantly. Maximum Subarray uses it, and so do dozens of other problems.
The first idea most people reach for feels obvious. Try every buy day with every later sell day. Keep the best profit you find.
It is slow, but it is honest. Nothing gets missed, because nothing gets skipped.
best = 0
for i from 0 to n-1: // the buy day
for j from i+1 to n-1: // the sell day
profit = prices[j] - prices[i]
if profit > best:
best = profit
return bestPicture two fingers on the array again. Your left finger picks the buy day. Your right finger slides across every later day looking for a good sale.
Why does best start at 0 rather than something tiny? Because a losing trade is never worth taking. Starting at 0 quietly encodes the rule “do nothing if nothing pays”.
Also notice there is no early return here. A big profit might still be waiting three days ahead, so the loops must finish.
public class StockBrute {
public static int maxProfit(int[] prices) {
int best = 0;
for (int i = 0; i < prices.length; i++) {
for (int j = i + 1; j < prices.length; j++) {
int profit = prices[j] - prices[i];
if (profit > best) {
best = profit;
}
}
}
return best;
}
public static void main(String[] args) {
int[] prices = { 7, 1, 5, 3, 6, 4 };
System.out.println(maxProfit(prices)); // 5
}
}Each line here does one small job. Let us walk down them slowly.
One detail deserves a second look. The inner loop shrinks as i moves right.
Add those up and you get fifteen checks for six prices. That number grows fast, and it is the weakness we fix next.
Let us trace prices = [7, 1, 5, 3, 6, 4]. Watch both pointers move and watch best climb.
| Step | i (buy day) | prices[i] | j (sell day) | prices[j] | Profit | best after |
|---|---|---|---|---|---|---|
| 1 | 0 | 7 | 1 | 1 | -6 | 0 |
| 2 | 0 | 7 | 2 | 5 | -2 | 0 |
| 3 | 0 | 7 | 3 | 3 | -4 | 0 |
| 4 | 0 | 7 | 4 | 6 | -1 | 0 |
| 5 | 0 | 7 | 5 | 4 | -3 | 0 |
| 6 | 1 | 1 | 2 | 5 | 4 | 4 |
| 7 | 1 | 1 | 3 | 3 | 2 | 4 |
| 8 | 1 | 1 | 4 | 6 | 5 | 5 |
| 9 | 1 | 1 | 5 | 4 | 3 | 5 |
| 10 | 2 | 5 | 3 | 3 | -2 | 5 |
| 11 | 2 | 5 | 4 | 6 | 1 | 5 |
| 12 | 2 | 5 | 5 | 4 | -1 | 5 |
| 13 | 3 | 3 | 4 | 6 | 3 | 5 |
| 14 | 3 | 3 | 5 | 4 | 1 | 5 |
| 15 | 4 | 6 | 5 | 4 | -2 | 5 |
Fifteen steps ran, and only two of them changed anything. Let us break that into phases.
Now look at the wasted effort. Thirteen of the fifteen steps produced nothing at all.
That last point is the key. Once you have seen a price of 1, buying at 5 makes no sense. The brute force keeps trying anyway.
Brute force tests every valid pair of days. For n prices that is about n times n over two checks.
Six prices needed fifteen steps. Twelve prices would need sixty six. A year of daily prices would need over sixty thousand.
| 💡 Interview Insight A common follow-up is whether you can break out of the loops early. You cannot here, because a bigger profit may still be waiting. Explaining why an early exit is unsafe scores as well as writing the optimal code. |
The dry run above handed us the fix. Buying at 5 was pointless once a price of 1 sat behind us.
So let us keep only the cheapest price we have seen. Then, on each day, we pretend we bought on that cheapest day and sell today.
This version still uses two variables and one loop. We write it in a slightly long form first, because the compact version hides what is happening.
minPrice = prices[0]
best = 0
for i from 1 to n-1:
todayProfit = prices[i] - minPrice
if todayProfit > best:
best = todayProfit
if prices[i] < minPrice:
minPrice = prices[i]
return bestOne loop now, and two jobs inside it. Read the order of those jobs carefully.
Why compute the profit before updating minPrice? Because you cannot buy and sell on the same day. If today became the new minimum first, the profit would come out as zero and hide a real answer.
There is a nice safety net built in here. When today is the cheapest price, todayProfit turns negative, so best simply refuses it.
public class StockTrackMin {
public static int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
int minPrice = prices[0];
int best = 0;
for (int i = 1; i < prices.length; i++) {
int todayProfit = prices[i] - minPrice;
if (todayProfit > best) {
best = todayProfit;
}
if (prices[i] < minPrice) {
minPrice = prices[i];
}
}
return best;
}
public static void main(String[] args) {
int[] prices = { 7, 1, 5, 3, 6, 4 };
System.out.println(maxProfit(prices)); // 5
}
}The shape is simple, so let us focus on why each piece exists.
Compare this with the brute force. The inner loop is gone completely, replaced by a single subtraction.
Same six prices. Watch minPrice drop once, then stay put.
| Step | i (today) | prices[i] | minPrice before | todayProfit | best after | minPrice after |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 7 | 1 – 7 = -6 | 0 | 1 |
| 2 | 2 | 5 | 1 | 5 – 1 = 4 | 4 | 1 |
| 3 | 3 | 3 | 1 | 3 – 1 = 2 | 4 | 1 |
| 4 | 4 | 6 | 1 | 6 – 1 = 5 | 5 | 1 |
| 5 | 5 | 4 | 1 | 4 – 1 = 3 | 5 | 1 |
Five steps instead of fifteen. Let us look at what happened in each one.
Step 1 is the interesting one. The profit was negative, yet that step still did useful work.
Notice also that minPrice never rose. Once 1 was found on day 1, it stayed for the rest of the run. That is what a running minimum does.
| 💡 Interview Insight Expect a question about seeding minPrice with Integer.MAX_VALUE instead of prices[0]. Both work, and MAX_VALUE lets you start the loop at index 0 and drop the empty-array guard. Mention that you picked prices[0] for readability, and the trade-off shows judgement. |
The version above is already O(n). So what is left to improve?
Nothing about speed, honestly. What changes is the shape of the code. Interviewers expect this tighter form, and it is worth being able to write it without thinking.
The idea stays identical. We just express both updates with Math.min and Math.max, and we start the loop at day 0.
minPrice = a very large number
best = 0
for each price in prices:
minPrice = min(minPrice, price)
best = max(best, price - minPrice)
return bestFour lines of logic now. But one detail flipped, and it trips people up.
Hold on. Did we not just say the opposite in section 5.2? We did, and the difference is worth understanding.
When today is the cheapest price, minPrice becomes today price. The profit then works out as today minus today, which is 0. Since best already sits at 0 or higher, that zero changes nothing.
So updating first is safe here, but only because best can never drop below 0. The two versions agree on every input.
public class StockOnePass {
public static int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int best = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
best = Math.max(best, price - minPrice);
}
return best;
}
public static void main(String[] args) {
int[] prices = { 7, 1, 5, 3, 6, 4 };
System.out.println(maxProfit(prices)); // 5
}
}This version says the most in the fewest lines, so let us go gently.
Two things make this version pleasant to write under pressure.
Set the two versions side by side and the equivalence becomes clear.
// Section 5 version (update min last) int todayProfit = prices[i] - minPrice; if (todayProfit > best) best = todayProfit; if (prices[i] < minPrice) minPrice = prices[i]; // Section 6 version (update min first) minPrice = Math.min(minPrice, price); best = Math.max(best, price - minPrice);
Both compute the same answer on every input. The second is shorter, and the first is easier to explain out loud.
One final trace through the same prices. Watch minPrice and best evolve together.
| Step | Day | price | minPrice before | minPrice after | price – minPrice | best after |
|---|---|---|---|---|---|---|
| 1 | 0 | 7 | MAX_VALUE | 7 | 7 – 7 = 0 | 0 |
| 2 | 1 | 1 | 7 | 1 | 1 – 1 = 0 | 0 |
| 3 | 2 | 5 | 1 | 1 | 5 – 1 = 4 | 4 |
| 4 | 3 | 3 | 1 | 1 | 3 – 1 = 2 | 4 |
| 5 | 4 | 6 | 1 | 1 | 6 – 1 = 5 | 5 |
| 6 | 5 | 4 | 1 | 1 | 4 – 1 = 3 | 5 |
Six steps, one per day, and no nesting anywhere. Here is what each day did.
Steps 1 and 2 are worth studying. Both produced a profit of exactly 0.
There is a quiet promise hidden in this loop. Every possible trade gets considered exactly once, always from the sell side looking back at the cheapest buy. You never miss a pair, and you never check one twice.
Same prices, same answer, three very different amounts of work.
| Approach | Steps taken | Times best changed | Extra memory |
|---|---|---|---|
| Brute force | 15 pair checks | 2 (at steps 6 and 8) | None |
| Track minimum | 5 day steps | 2 (at steps 2 and 4) | Two int values |
| One-pass scan | 6 day steps | 2 (at steps 3 and 5) | Two int values |
The gap grows quickly with size. At six prices it looks small. At six thousand it is the difference between instant and unusable.
| 💡 Interview Insight A classic follow-up asks how you would also report the buy and sell days, not just the profit. Keep two extra variables that record the index of minPrice and the index where best last improved. Answering that smoothly shows you understand the loop rather than memorising it. |
Tables are exact, but a sketch often lands faster. Here is the same one-pass trace drawn by hand.

The prices sit along the top with their day numbers. Each block on the left shows the day being visited and the question being asked. The panel on the right shows minPrice and maxProfit after that day.
Follow the colour cues as you read it.
Some people read pictures faster than tables. Use whichever version clicks, or sketch your own on paper while you follow the code.
All three return the correct answer. They just pay very different prices for it.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O(n squared) | O(1) | Easy to write, unusable on long price histories |
| Track minimum | O(n) | O(1) | Fast and very easy to explain out loud |
| One-pass scan | O(n) | O(1) | Same speed, tighter code, the expected answer |
Notice that space stays at O(1) across all three. Unlike Two Sum, this problem needs no map at all. You only carry two numbers no matter how long the array grows.
In an interview, start with brute force to prove you understand the rules. Then explain the wasted work you spotted in the trace. Finally tighten it into the one-pass scan.
That climb from slow to fast is the story interviewers came to hear. Jumping straight to the optimal answer skips the reasoning they are trying to observe.
| 💡 Interview Insight If you are asked to handle many trades instead of one, the problem becomes Best Time to Buy and Sell Stock II. There you add up every upward step, which is a much simpler loop. Knowing the family of variants is worth more than memorising one solution. |
A handful of small traps catch beginners on this one. Keep them in mind.
Run those last three cases through your code before you say you are finished. They catch more bugs than any normal input will.
Best Time to Buy and Sell Stock in Java looks like a warm-up, and in a way it is. But the running minimum pattern inside it is a real skill.
Our six-price trace showed the payoff plainly. Brute force needed fifteen comparisons. The one-pass scan needed six simple steps and no extra memory at all.
So take the pattern, not just the answer. Write brute force first when you feel stuck. Then read your own dry run and hunt for repeated work.
That habit is what turned fifteen steps into six here. It will do the same for Maximum Subarray, for sliding window problems, and for plenty more waiting further down the list.
A: The optimal one-pass scan runs in O(n) time and O(1) space. It visits each price once and carries only two integers, minPrice and maxProfit. The brute force version runs in O(n squared) because it nests one loop inside another.
A: Because order matters. You must buy before you sell. In the array [7, 1, 5, 3, 6, 4] the highest price 7 sits on day 0, before the lowest price 1 on day 1. Selling before buying is not allowed, so the answer is 5 rather than 6.
A: The answer is 0. For an input like [7, 6, 4, 3, 1] every possible trade loses money, so you simply do not trade. This is why maxProfit starts at 0 rather than at Integer.MIN_VALUE.
A: Both orders give the same answer. If you update minPrice first, then on a new low the profit becomes price minus itself, which is 0 and cannot lower a maxProfit that already sits at 0 or above. If you update it last, the profit on a new low comes out negative and gets rejected the same way.
A: Keep two extra variables. Store the index whenever minPrice changes, and store both that index and the current index whenever maxProfit improves. This adds a few lines but does not change the O(n) time or O(1) space.
A: This problem allows exactly one trade. The second version allows unlimited trades, so you simply add up every upward step between consecutive days. That variant is actually easier to code once you understand this one.
javahandson.com | DSA Series | Arrays & Strings