Part 4 · Lesson 24 · 24 min read

Kadane's Algorithm

The maximum subarray sum in a single pass, and the stock-trading problems built on the same idea.

On this page

  1. The largest sum of any subarray
  2. Brute force: every start, every end
  3. The key question: extend or start again?
  4. Traced: Kadane on the classic example
  5. Returning the subarray itself
  6. Best time to buy and sell stock
  7. Maximum product: track the minimum too
  8. Circular subarrays
  9. Practice questions (6)
  10. Make it stick
  11. What's next

The largest sum of any subarray

You get an array with positive and negative numbers. Find the subarray with the largest sum. (A subarray is a block of items that sit next to each other.) For [-2, 1, -3, 4, -1, 2, 1, -5, 4] the answer is 6, from [4, -1, 2, 1]. Notice that this block includes a −1. Sometimes it is worth accepting a small loss to reach bigger gains later.

A sliding window does not work here (Lesson 23). With negative numbers, shrinking the window can make the sum bigger or smaller, so the window rules break. Kadane's algorithm solves the problem in one pass with a different idea. It is named after Jay Kadane, who found it.

Brute force: every start, every end

JavaScript
function maxSubArrayBrute(nums) {  let best = -Infinity;  for (let i = 0; i < nums.length; i++) {    let sum = 0;    for (let j = i; j < nums.length; j++) {      sum += nums[j];                    // sum of nums[i..j]      best = Math.max(best, sum);    }  }  return best;}console.log(maxSubArrayBrute([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6

For each start, we keep a running sum as the end moves. This avoids an O(n³) solution (adding up every subarray from scratch). But it still costs O(n²) steps.

The key question: extend or start again?

Walk from left to right and keep one number called cur. It is the best sum of a subarray that ends exactly at the current index. For the next item x there are only two choices:

  • Extend the best subarray ending at the previous index: cur + x.
  • Start again with x alone: x.
Kadane's algorithm in one line

cur = Math.max(x, cur + x). If the sum so far is negative, it can only make the new sum smaller. So drop it and start again from x. The answer is the largest cur you ever see.

Traced: Kadane on the classic example

step 1 / 15
Set up
cur = -2

cur is the best sum of a subarray that ends exactly here. Only one subarray ends at index 0.

1const nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4];▶let cur = nums[0];3let best = nums[0];4for (let i = 1; i < nums.length; i++) {5  cur = Math.max(nums[i], cur + nums[i]);6  best = Math.max(best, cur);7}8console.log(best);
Variables
nums[-2, 1, -3, 4, -1, 2, 1, -5, 4]
cur-2
Console
(nothing printed yet)
At each index, cur answers: what is the best subarray that ends here? best keeps the largest of those answers. · interactive
Dry run · cur and best at every index
ixcurbestchoice
0−2−2−2first item
1111start again
2−3−21extend
3444start again
4−134extend
5255extend
6166extend
7−516extend
8456extend

It is important to start cur and best at nums[0], not at 0. Take an array where every number is negative, like [-3, -1, -2]. The answer is −1. If you start at 0, the code wrongly returns 0. This is also your first dynamic programming solution. Dynamic programming means you build the answer for each step from the answer for the previous step (Part 14).

Returning the subarray itself

Sometimes you must return the subarray itself, not just its sum. To do this, remember where the current run started. Whenever you find a new best sum, save that start position:

JavaScript
function maxSubArrayRange(nums) {  let cur = nums[0], best = nums[0];  let start = 0, bestStart = 0, bestEnd = 0;  for (let i = 1; i < nums.length; i++) {    if (nums[i] > cur + nums[i]) {       // starting again is better      cur = nums[i];      start = i;    } else {      cur += nums[i];    }    if (cur > best) {      best = cur;      bestStart = start;      bestEnd = i;    }  }  return { best, sub: nums.slice(bestStart, bestEnd + 1) };}console.log(maxSubArrayRange([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // { best: 6, sub: [ 4, -1, 2, 1 ] }

Best time to buy and sell stock

You get the price of a stock for each day. Buy on one day and sell on a later day, to make the largest profit. The same “best ending here” thinking works. If you sell today, the best you can do is to have bought at the cheapest price seen so far. Keep track of that lowest price as you walk through the prices.

JavaScript
function maxProfit(prices) {  let minPrice = Infinity, best = 0;  for (const p of prices) {    minPrice = Math.min(minPrice, p);        // cheapest day to buy so far    best = Math.max(best, p - minPrice);     // sell today?  }  return best;}console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5   buy at 1, sell at 6console.log(maxProfit([7, 6, 4, 3, 1]));    // 0   never profitable

This is Kadane's idea in another form. The profit from day i to day j equals the sum of the daily price changes between them. So the best trade is the maximum subarray of the list of daily changes.

Maximum product: track the minimum too

For the largest product, one number is not enough. A very negative product can become the largest positive product when you multiply it by another negative number. So keep two numbers: the largest product and the smallest product that end at the current index:

JavaScript
function maxProduct(nums) {  let hi = nums[0], lo = nums[0], best = nums[0];  for (let i = 1; i < nums.length; i++) {    const x = nums[i];    const candidates = [x, hi * x, lo * x];    hi = Math.max(...candidates);           // largest product ending here    lo = Math.min(...candidates);           // smallest (most negative) product ending here    best = Math.max(best, hi);  }  return best;}console.log(maxProduct([2, 3, -2, 4]));     // 6console.log(maxProduct([-2, 3, -4]));       // 24   the two negatives multiply
Dry run · maxProduct([-2, 3, -4])
xcandidateshilobest
−2—−2−2−2
33, −6, −63−63
−4−4, −12, 2424−1224

The smallest product −6 (from −2 × 3) became the largest, 24, when multiplied by −4.

Circular subarrays

Now imagine the array is a circle, so a subarray may wrap from the end back to the start. A wrapping subarray is everything except a block in the middle. So the best wrapping sum is total − (the minimum subarray sum). Run Kadane for the maximum and for the minimum together. Then take the better of the two cases:

JavaScript
function maxSubarraySumCircular(nums) {  let total = 0;  let curMax = 0, bestMax = -Infinity;  let curMin = 0, bestMin = Infinity;  for (const x of nums) {    total += x;    curMax = Math.max(x, curMax + x);    bestMax = Math.max(bestMax, curMax);    curMin = Math.min(x, curMin + x);    bestMin = Math.min(bestMin, curMin);  }  if (bestMax < 0) return bestMax;           // all negative: the wrap trick would give 0  return Math.max(bestMax, total - bestMin);}console.log(maxSubarraySumCircular([5, -3, 5]));    // 10   wraps around: [5, 5]console.log(maxSubarraySumCircular([-3, -2, -3]));  // -2

There is one edge case. If every value is negative, the minimum subarray is the whole array. Then total − bestMin would be 0, which means an empty subarray, and that is not allowed. In this case, return the normal maximum.

Practice questions

For each question, first say what “the best answer that ends at index i” means. Then write the one-line update.

Q1Maximum subarrayMedium

Return the largest sum of any subarray that has at least one item.

Example 1
Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Output: 6
Explanation: [4, −1, 2, 1].
Example 2
Input: [1]
Output: 1
Explanation: One item.
Example 3
Input: [-3, -1, -2]
Output: -1
Explanation: Edge case: all numbers are negative. The best subarray is the single largest value.
Stuck? Open one hint at a time
Hint 1
The best sum that ends at i is max(nums[i], the best sum that ends at i − 1, plus nums[i]).
Show all 2 solutions with explanations
Approach 1Every start, running sum
The idea

This is the brute force method. It tries every start and every end.

JavaScript
function maxSubArray(nums) {  let best = -Infinity;  for (let i = 0; i < nums.length; i++) {    let s = 0;    for (let j = i; j < nums.length; j++) best = Math.max(best, (s += nums[j]));  }  return best;}console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6
How it works

O(n²). It is too slow for 105 values.

Approach 2Kadane
The idea

At each item, extend the current run or start again. Keep the best sum you have seen.

JavaScript
function maxSubArray(nums) {  let cur = nums[0], best = nums[0];  for (let i = 1; i < nums.length; i++) {    cur = Math.max(nums[i], cur + nums[i]);    best = Math.max(best, cur);  }  return best;}console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6console.log(maxSubArray([1]));                             // 1console.log(maxSubArray([-3, -1, -2]));                    // -1
How it works

O(n) time, O(1) space.

Which approach should you use?

Kadane is the answer interviewers expect. Be ready to return the start and end indices as a follow-up question. (LeetCode 53.)

Q2Best time to buy and sell stockEasy

Choose one day to buy and a later day to sell. Return the largest profit. If you cannot make a profit, return 0.

Example 1
Input: [7, 1, 5, 3, 6, 4]
Output: 5
Explanation: Buy at 1 (day 1), sell at 6 (day 4).
Example 2
Input: [7, 6, 4, 3, 1]
Output: 0
Explanation: The prices only fall, so do not trade.
Stuck? Open one hint at a time
Hint 1
You must buy before you sell. For each day, ask: what is the cheapest earlier price?
Show all 2 solutions with explanations
Approach 1Every pair of days
The idea

Try every buy day with every later sell day.

JavaScript
function maxProfit(prices) {  let best = 0;  for (let i = 0; i < prices.length; i++)    for (let j = i + 1; j < prices.length; j++)      best = Math.max(best, prices[j] - prices[i]);  return best;}console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5
How it works

O(n²).

Approach 2Minimum so far
The idea

Keep the lowest price seen so far. On each day, try to sell.

JavaScript
function maxProfit(prices) {  let minPrice = Infinity, best = 0;  for (const p of prices) {    minPrice = Math.min(minPrice, p);    best = Math.max(best, p - minPrice);  }  return best;}console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5console.log(maxProfit([7, 6, 4, 3, 1]));    // 0
How it works

O(n) time, O(1) space. First update the lowest price, then try to sell. This way you never sell before you buy. (If you buy and sell on the same day, the profit is 0.)

Which approach should you use?

This is a very common first question. (LeetCode 121.)

Q3Buy and sell stock, many tradesMedium

You may buy and sell as many times as you like, but you can hold at most one share at a time. Return the largest profit.

Example 1
Input: [7, 1, 5, 3, 6, 4]
Output: 7
Explanation: Buy 1 sell 5 (+4), buy 3 sell 6 (+3).
Example 2
Input: [1, 2, 3, 4, 5]
Output: 4
Explanation: Buy 1 sell 5 — the same as collecting every daily rise.
Stuck? Open one hint at a time
Hint 1
You can trade as many times as you like, and you hold at most one share. So you can collect every price rise.
Show the solution with explanations
Approach 1Sum of positive daily changes
The idea

Add prices[i] − prices[i − 1] whenever the result is positive.

JavaScript
function maxProfit(prices) {  let profit = 0;  for (let i = 1; i < prices.length; i++) {    if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];  }  return profit;}console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 7console.log(maxProfit([1, 2, 3, 4, 5]));    // 4
How it works

O(n) time. A long rise from 1 to 5 earns the same as its daily steps, 1+1+1+1. So splitting a trade into smaller trades never loses money.

Which approach should you use?

Read the rules with care. One trade (Question 2) and many trades need very different solutions. (LeetCode 122.)

Q4Maximum product subarrayMedium

Return the largest product of any subarray that has at least one item.

Example 1
Input: [2, 3, -2, 4]
Output: 6
Explanation: [2, 3].
Example 2
Input: [-2, 0, -1]
Output: 0
Explanation: The 0 breaks any product; [0] itself gives 0.
Example 3
Input: [-2, 3, -4]
Output: 24
Explanation: All three: the negatives cancel.
Stuck? Open one hint at a time
Hint 1
A negative number turns the smallest product into the largest one. So keep both.
Show the solution with explanations
Approach 1Kadane with max and min
The idea

For each x, the new largest and smallest products come from three choices: x alone, hi × x and lo × x.

JavaScript
function maxProduct(nums) {  let hi = nums[0], lo = nums[0], best = nums[0];  for (let i = 1; i < nums.length; i++) {    const x = nums[i];    const a = hi * x, b = lo * x;    hi = Math.max(x, a, b);    lo = Math.min(x, a, b);    best = Math.max(best, hi);  }  return best;}console.log(maxProduct([2, 3, -2, 4])); // 6console.log(maxProduct([-2, 0, -1]));   // 0console.log(maxProduct([-2, 3, -4]));   // 24
How it works

O(n) time, O(1) space. Work out a and b before you change hi. The new lo must use the old hi.

Which approach should you use?

This is a classic follow-up to Question 1. (LeetCode 152.)

Q5Maximum sum circular subarrayMedium

The array is circular, so the end connects back to the start. Return the largest sum of a subarray that has at least one item. The subarray may wrap around.

Example 1
Input: [1, -2, 3, -2]
Output: 3
Explanation: [3].
Example 2
Input: [5, -3, 5]
Output: 10
Explanation: Wrap around: [5, 5].
Example 3
Input: [-3, -2, -3]
Output: -2
Explanation: All numbers are negative, so the best is −2 on its own.
Stuck? Open one hint at a time
Hint 1
The best subarray either does not wrap (use normal Kadane), or it wraps. If it wraps, its sum is the total minus the smallest middle part.
Show the solution with explanations
Approach 1Max Kadane and min Kadane together
The idea

Make one pass. Work out the total, the best maximum and the best minimum.

JavaScript
function maxSubarraySumCircular(nums) {  let total = 0, curMax = 0, bestMax = -Infinity, curMin = 0, bestMin = Infinity;  for (const x of nums) {    total += x;    curMax = Math.max(x, curMax + x);    bestMax = Math.max(bestMax, curMax);    curMin = Math.min(x, curMin + x);    bestMin = Math.min(bestMin, curMin);  }  return bestMax < 0 ? bestMax : Math.max(bestMax, total - bestMin);}console.log(maxSubarraySumCircular([1, -2, 3, -2])); // 3console.log(maxSubarraySumCircular([5, -3, 5]));     // 10console.log(maxSubarraySumCircular([-3, -2, -3]));   // -2
How it works
Dry run · [5, −3, 5]
CaseValue
Best non-wrapping (Kadane)7 ([5, −3, 5])
Total7
Smallest subarray−3
Best wrapping = 7 − (−3)10
Which approach should you use?

O(n) time, O(1) space. The key idea is to change “wraps around” into “everything except a middle part”. (LeetCode 918.)

Q6Maximum absolute sum of any subarrayMedium

Return the largest absolute value of the sum of any subarray. The empty subarray, with sum 0, is allowed.

Example 1
Input: [1, -3, 2, 3, -4]
Output: 5
Explanation: [2, 3] has sum 5. The most negative subarray sum is only −4, so 5 is the answer.
Example 2
Input: [2, -5, 1, -4, 3, -2]
Output: 8
Explanation: [−5, 1, −4] has sum −8, and |−8| = 8.
Stuck? Open one hint at a time
Hint 1
The absolute value of a sum is its size without the minus sign. The largest one comes from either the largest sum or the smallest (most negative) sum.
Show the solution with explanations
Approach 1Max and min Kadane
The idea

Run both versions in one pass. Return the larger of the best maximum and −(the best minimum).

JavaScript
function maxAbsoluteSum(nums) {  let curMax = 0, curMin = 0, best = 0;  for (const x of nums) {    curMax = Math.max(x, curMax + x);    curMin = Math.min(x, curMin + x);    best = Math.max(best, curMax, -curMin);  }  return best;}console.log(maxAbsoluteSum([1, -3, 2, 3, -4]));     // 5console.log(maxAbsoluteSum([2, -5, 1, -4, 3, -2])); // 8
How it works

O(n) time, O(1) space. There is another way to see it. The answer equals (largest prefix sum) − (smallest prefix sum), using Lesson 20.

Which approach should you use?

When you know the max version and the min version of Kadane, many variations take only a few lines. (LeetCode 1749.)

Make it stick

Make it stick — close this page, then
  1. Write Kadane's algorithm from memory and explain “extend or start again”.
  2. Say why cur and best start at nums[0] and not 0.
  3. Explain why the maximum product needs the minimum product too.
  4. Explain the circular case using “total minus the minimum subarray”.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

Lesson 25 finishes Part 4 with 2-D arrays. A 2-D array is a grid with rows and columns. You will transpose a matrix (swap its rows and columns), rotate it, read it in a spiral, and search a sorted matrix. These are all common interview questions.

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Part of the study guide

Related series