← Back to DevBytes

Solving Best Time to Buy and Sell Stock in JavaScript: Step-by-Step Guide

Introduction to the Best Time to Buy and Sell Stock Problem

The "Best Time to Buy and Sell Stock" problem is one of the most iconic algorithmic challenges you will encounter in coding interviews and competitive programming. It appears frequently on platforms like LeetCode, HackerRank, and in technical interviews at major tech companies. The problem tests your ability to think about array traversal, optimization, and recognizing when a seemingly complex problem can be solved with an elegant, efficient approach.

At its core, the problem asks you to determine the maximum profit you can achieve by buying and selling a single share of stock, given a chronological list of daily prices. While a brute force solution may come to mind immediately, the real challenge lies in optimizing your solution to run efficiently, even with large datasets.

What Is the Best Time to Buy and Sell Stock Problem?

The problem statement is deceptively simple. You are given an array prices where prices[i] represents the price of a given stock on day i. You want to maximize your profit by choosing a single day to buy one share and choosing a different day in the future to sell that share. Your task is to return the maximum profit you can achieve. If no profit is possible, you return zero.

For example, consider the following input:

const prices = [7, 1, 5, 3, 6, 4];

Here, the optimal strategy is to buy on day 1 (when the price is 1) and sell on day 4 (when the price is 6), yielding a profit of 5. No other combination produces a higher profit.

Key Constraints to Understand

Why This Problem Matters

Beyond being a common interview question, this problem teaches several fundamental programming concepts. First, it demonstrates the importance of recognizing patterns in data. Second, it highlights the trade-off between time complexity and space complexity. Finally, it introduces the concept of maintaining state while iterating through a data structure, a technique you will use repeatedly in real-world applications.

In practical terms, similar logic is used in financial software for analyzing historical price data, in inventory management systems for identifying optimal purchasing windows, and in any domain where you need to find the maximum difference between two values in a sequence with ordering constraints.

The Brute Force Approach

The most intuitive solution is to compare every possible pair of buy and sell days. For each day, you consider buying at that price and then check every subsequent day as a potential sell day. You track the maximum profit found across all combinations.

function maxProfitBruteForce(prices) {
  let maxProfit = 0;

  for (let i = 0; i < prices.length; i++) {
    for (let j = i + 1; j < prices.length; j++) {
      const profit = prices[j] - prices[i];
      if (profit > maxProfit) {
        maxProfit = profit;
      }
    }
  }

  return maxProfit;
}

console.log(maxProfitBruteForce([7, 1, 5, 3, 6, 4])); // Output: 5
console.log(maxProfitBruteForce([7, 6, 4, 3, 1]));    // Output: 0

While this solution works correctly, it has a time complexity of O(n²) because of the nested loops. For small arrays, this is fine, but when the input grows to tens of thousands of prices, the performance degrades rapidly. This is where optimization becomes essential.

The Optimized One-Pass Solution

The key insight for optimization is that you only need to traverse the array once. As you iterate through the prices, you keep track of the minimum price you have seen so far. At each step, you calculate the potential profit if you were to sell at the current price, having bought at that minimum price. You update the maximum profit whenever you find a better one.

This approach works because the minimum price seen so far always represents the best possible buying opportunity up to the current day. Any future selling price compared against this minimum gives you the best possible profit at that point in time.

function maxProfit(prices) {
  let minPrice = Infinity;
  let maxProfit = 0;

  for (let i = 0; i < prices.length; i++) {
    if (prices[i] < minPrice) {
      minPrice = prices[i];
    } else {
      const potentialProfit = prices[i] - minPrice;
      if (potentialProfit > maxProfit) {
        maxProfit = potentialProfit;
      }
    }
  }

  return maxProfit;
}

console.log(maxProfit([7, 1, 5, 3, 6, 4])); // Output: 5
console.log(maxProfit([7, 6, 4, 3, 1]));    // Output: 0
console.log(maxProfit([2, 4, 1]));          // Output: 2

This solution runs in O(n) time complexity because it only passes through the array once. The space complexity is O(1) since it uses only two variables regardless of the input size. This is a dramatic improvement over the brute force approach.

Breaking Down the Logic Step by Step

Let us trace through the example [7, 1, 5, 3, 6, 4] to understand exactly how the algorithm works:

The final answer is 5, which matches our expected output.

Handling Edge Cases

A robust solution must account for edge cases that could cause unexpected behavior. Let us examine some scenarios and how our function handles them.

function maxProfit(prices) {
  // Handle empty or single-element arrays
  if (!prices || prices.length < 2) {
    return 0;
  }

  let minPrice = Infinity;
  let maxProfit = 0;

  for (let i = 0; i < prices.length; i++) {
    if (prices[i] < minPrice) {
      minPrice = prices[i];
    } else {
      const potentialProfit = prices[i] - minPrice;
      if (potentialProfit > maxProfit) {
        maxProfit = potentialProfit;
      }
    }
  }

  return maxProfit;
}

console.log(maxProfit([]));        // Output: 0
console.log(maxProfit([5]));       // Output: 0
console.log(maxProfit([5, 5, 5])); // Output: 0
console.log(maxProfit([1, 2]));    // Output: 1
console.log(maxProfit([2, 1]));    // Output: 0

Adding the guard clause at the beginning ensures the function does not attempt to process invalid input. When the array has fewer than two elements, no transaction is possible, so returning zero is the correct behavior.

Using a For-Of Loop for Cleaner Code

JavaScript offers several ways to iterate over arrays. Using a for...of loop can make the code more readable, especially when you do not need the index. Here is the same algorithm written with this modern syntax:

function maxProfit(prices) {
  if (!prices || prices.length < 2) {
    return 0;
  }

  let minPrice = Infinity;
  let maxProfit = 0;

  for (const price of prices) {
    minPrice = Math.min(minPrice, price);
    maxProfit = Math.max(maxProfit, price - minPrice);
  }

  return maxProfit;
}

console.log(maxProfit([7, 1, 5, 3, 6, 4])); // Output: 5

This version leverages Math.min and Math.max to simplify the comparison logic. The result is functionally identical but more concise. Some developers prefer this style because it clearly expresses the intent of updating the minimum price and maximum profit at each step.

Tracking the Buy and Sell Days

In some variations of the problem, you may need to return not just the maximum profit but also the specific days on which you should buy and sell. This requires tracking additional state during the iteration.

function maxProfitWithDays(prices) {
  if (!prices || prices.length < 2) {
    return { profit: 0, buyDay: -1, sellDay: -1 };
  }

  let minPrice = Infinity;
  let minPriceDay = -1;
  let maxProfit = 0;
  let buyDay = -1;
  let sellDay = -1;

  for (let i = 0; i < prices.length; i++) {
    if (prices[i] < minPrice) {
      minPrice = prices[i];
      minPriceDay = i;
    } else {
      const potentialProfit = prices[i] - minPrice;
      if (potentialProfit > maxProfit) {
        maxProfit = potentialProfit;
        buyDay = minPriceDay;
        sellDay = i;
      }
    }
  }

  return { profit: maxProfit, buyDay, sellDay };
}

const result = maxProfitWithDays([7, 1, 5, 3, 6, 4]);
console.log(result);
// Output: { profit: 5, buyDay: 1, sellDay: 4 }

Notice that we update buyDay and sellDay only when we find a new maximum profit. This ensures the returned days correspond to the optimal transaction. The minPriceDay variable tracks the day of the current minimum price, which becomes the buy day whenever a new maximum profit is discovered.

Best Practices for Solving This Problem

When approaching this problem, whether in an interview or in production code, keep the following best practices in mind:

Common Mistakes to Avoid

Even experienced developers can make errors when solving this problem. One common mistake is initializing minPrice to prices[0] without first checking if the array is empty. This leads to undefined behavior when the input is invalid. Always validate your input before accessing elements.

Another frequent error is updating the maximum profit before updating the minimum price within the same iteration. This can cause the algorithm to consider buying and selling on the same day, which violates the problem constraints. The order of operations in the loop matters.

// Incorrect: updates maxProfit before minPrice, causing same-day transactions
function maxProfitIncorrect(prices) {
  let minPrice = Infinity;
  let maxProfit = 0;

  for (const price of prices) {
    maxProfit = Math.max(maxProfit, price - minPrice);
    minPrice = Math.min(minPrice, price);
  }

  return maxProfit;
}

// This actually works because Math.max with Infinity yields a negative number,
// but the logic is fragile and confusing. Prefer the explicit if-else structure.

While the above code technically produces correct results due to how Infinity behaves in arithmetic, the logic is less clear and can lead to subtle bugs when modified. Prefer the explicit structure that separates the two concerns.

Extending to Multiple Transactions

A natural extension of this problem allows multiple buy-sell transactions. While the single-transaction version is the foundation, understanding how to extend it broadens your problem-solving toolkit. Here is a brief example where you can buy and sell as many times as you want, but you can only hold one share at a time:

function maxProfitMultipleTransactions(prices) {
  let totalProfit = 0;

  for (let i = 1; i < prices.length; i++) {
    if (prices[i] > prices[i - 1]) {
      totalProfit += prices[i] - prices[i - 1];
    }
  }

  return totalProfit;
}

console.log(maxProfitMultipleTransactions([7, 1, 5, 3, 6, 4])); // Output: 7
// Buy at 1, sell at 5 (profit 4), buy at 3, sell at 6 (profit 3). Total: 7.

This greedy approach captures every upward price movement as a profit opportunity. It is a different problem with a different solution strategy, but it builds on the same foundation of array traversal and profit calculation.

Conclusion

The Best Time to Buy and Sell Stock problem is a perfect example of how a seemingly complex challenge can be reduced to an elegant, efficient solution through careful analysis. By moving from a brute force O(n²) approach to a single-pass O(n) algorithm, you demonstrate not only coding ability but also algorithmic thinking. The key takeaway is the technique of maintaining running state, such as the minimum price seen so far, while iterating through data. This pattern appears across countless problems in software development, from financial analysis to data processing pipelines. Master this problem, and you will be well-equipped to tackle a wide range of array-based algorithmic challenges in JavaScript and beyond.

— Ad —

Google AdSense will appear here after approval

← Back to all articles