Kadane's Algorithm
Find the maximum-sum contiguous subarray in one pass by tracking the best sum ending at each position.
Overview
Given an array of integers (possibly negative), find the contiguous subarray with the largest sum. For [-2, 1, -3, 4, -1, 2, 1, -5, 4] the answer is 6 from [4, -1, 2, 1]. Kadane's algorithm solves it in O(n) time and O(1) space.
It is a DP with a one-element state: "best sum of a subarray that ends exactly here". Because that state depends only on the previous position, the whole table collapses to a running variable. The same idea, with different combining rules, gives maximum product subarray, maximum circular subarray, and best-time-to-buy-and-sell-stock.
Intuition
A mental model before the formal terms.
Walk the array left to right carrying a running sum. At each element ask: "does the stuff I am carrying help me, or would I be better off starting fresh here?" If the carried sum is negative it can only drag the next element down, so drop it. For [-2, 1, -3, 4, …]: carry -2 → start fresh at 1 → 1 + (-3) = -2 → negative, so start fresh at 4 → 4 - 1 = 3 → 5 → 6 (best so far) → 1 → 5. The best seen was 6.
Equivalently: using Prefix Sum P, the best subarray ending at i is P[i] - min(P[0..i-1]). Kadane tracks that running minimum implicitly through the "reset to zero" rule.
How it works
- State:
best_ending_here[i]= maximum sum of a non-empty subarray whose last element isnums[i]. - Transition:
best_ending_here[i] = nums[i] + max(best_ending_here[i-1], 0)— extend the previous best if it is positive, else start a new subarray ati. Equivalentlymax(nums[i], best_ending_here[i-1] + nums[i]). - Base case:
best_ending_here[0] = nums[0]. - Iteration order:
ifrom 1 ton-1. - Answer location:
max over i of best_ending_here[i]— tracked as a runningbest(initialized tonums[0], not 0). The subarray with the global maximum may end anywhere. - Space optimization: only the previous state is read, so a single variable
curreplaces the array. To recover the subarray, record the start index whencurresets and the(start, i)pair whenbestimproves.
Why it works
Optimal substructure: any subarray ending at i is either [i, i] alone or a subarray ending at i-1 extended by nums[i]. In the second case the prefix must be the *best* subarray ending at i-1 — otherwise swapping in the better one improves the sum. So the max over these two options is exact, and it reduces to nums[i] + max(prev, 0).
Every subarray ends somewhere, so the global maximum is the max of the per-position maxima; scanning once and keeping the largest value seen finds it.
All-negative edge case: if every element is negative, every cur is negative and the "reset" to nums[i] happens at every step. With best initialized to nums[0] the answer is the largest single element, which is correct for the non-empty version. Initializing best = 0 would silently answer "empty subarray, sum 0" — right only if the problem allows an empty subarray.
Recognition
How to tell a problem wants this.
- "Maximum sum contiguous subarray", "largest sum of consecutive elements", "best gain over a window of any length".
- Values can be negative (otherwise the whole array is trivially the answer).
- A single linear pass is expected (
nup to10^5–10^6).
Interactive visualization
Play, step, change the input. ← → and space work too.
1cur = a[0], best = a[0], start = 02for i in 1 .. n-1:3 if cur < 0: cur = a[i]; start = i4 else: cur += a[i]5 if cur > best: best = cur; bestRange = [start, i]6return bestPseudocode
1cur = best = nums[0]2for i in 1..n-1:3 cur = max(nums[i], cur + nums[i])4 best = max(best, cur)5return bestImplementations
1# Kadane: the maximum-sum contiguous subarray in one pass. The insight is2# that the best subarray ending at i either extends the best one ending at3# i-1, or starts fresh at i — so one number carries all the history needed.4 5 61 · best = answer so far, cur = best sum of a subarray ending here7def max_subarray_sum(a: list[int]) -> int:8 if not a:9 return 0 # caller's convention10 best = cur = a[0]11 for x in a[1:]:122 · Extend, or restart at x — whichever is larger13 cur = max(x, cur + x)14 best = max(best, cur)15 return best16 17 183 · Tracking the boundaries costs two extra variables, not another pass19def max_subarray_range(a: list[int]) -> tuple[int, int, int]:20 if not a:21 return 0, -1, -122 best = cur = a[0]23 best_lo = best_hi = cur_lo = 024 for i in range(1, len(a)):25 if cur + a[i] < a[i]:26 cur = a[i]27 cur_lo = i # restarting: the new subarray begins here28 else:29 cur += a[i]30 if cur > best:31 best, best_lo, best_hi = cur, cur_lo, i32 return best, best_lo, best_hi33 34 354 · Circular arrays: the answer is either normal, or total minus the minimum36def max_subarray_circular(a: list[int]) -> int:37 if not a:38 return 039 total = max_cur = max_best = min_cur = min_best = a[0]40 for x in a[1:]:41 total += x42 max_cur = max(x, max_cur + x)43 max_best = max(max_best, max_cur)44 min_cur = min(x, min_cur + x)45 min_best = min(min_best, min_cur)465 · If every element is negative, total - min_best is 0 (the empty wrap)47 return max_best if max_best < 0 else max(max_best, total - min_best)best = cur = a[0]chains the assignment, which is idiomatic for two variables that start equal.for x in a[1:]iterates the values directly, but note that the slice *copies* the list —itertools.islice(a, 1, None)avoids that for large inputs.max_subarray_rangeuses index iteration because it needsifor the boundaries, and updates all three best values in one tuple assignment.total = max_cur = max_best = min_cur = min_best = a[0]initialises five variables from one value, which is compact and safe because integers are immutable.- The final conditional expression handles the all-negative case, where the complement of the minimum subarray is empty.
The slice in the loop header is the one place this version allocates; islice removes it without changing the shape of the code.
a[1:]copies;itertools.islice(a, 1, None)is the lazy equivalent and matters for large lists.- Chained assignment (
a = b = value) evaluates the right-hand side once and binds it to every name — safe for immutable values, aliasing for mutable ones. - Python integers are unbounded, so there is no overflow concern in the accumulators — unlike C++.
max()andmin()on two arguments are C-level calls and are fine in a hot loop;max()over a generator is a different cost profile.
- Slicing in the loop header for a very large list and quietly doubling memory.
- Initialising
best = 0, which is wrong for an all-negative array. - Using chained assignment with a mutable value (
a = b = []), which aliases — harmless here because the values are integers.
- Accumulator width: C++ needs
long longto avoid overflow on long arrays of large values, Python is unbounded, and JS/TS are exact only to 2^53. - Returning three values: C++ uses
std::tuple(unpacked with structured bindings), Python a tuple, and JS/TS an object — the object is the only one that resists positional mix-ups. - Iterating "all but the first": C++ indexes from 1, JS/TS index from 1, and Python
a[1:]copies unlessitertools.isliceis used — a hidden allocation the other three do not have. - The empty-input convention has no cross-language standard; every version here documents its choice explicitly rather than assuming.
Complexity
The divide-and-conquer alternative is O(n log n); the prefix-sum formulation is also O(n) but needs the running minimum prefix.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Maximum (or minimum) sum contiguous subarray in one pass.
- Variants with the same "extend or restart" structure: maximum product subarray (track max and min), maximum circular subarray (
total - min subarray), best time to buy and sell stock (differences array). - 2D maximum-sum submatrix: fix a pair of rows, collapse columns into a 1D array, run Kadane —
O(n²·m).
- The subarray length is constrained (exactly
k, at mostk) — use Sliding Window (Fixed Size) or prefix sums with a Monotonic Queue. - The problem asks for a sub*sequence* (non-contiguous) — then just sum the positives.
- Queries over many ranges — precompute with a Segment Tree storing (sum, best prefix, best suffix, best) per node.
Alternatives
Common mistakes
- Initializing
best = 0for an all-negative array, returning 0 instead of the largest element. - Resetting
curto 0 instead of tonums[i]— same all-negative bug in another form. - Returning
cur(the sum ending at the last element) instead ofbest. - In the product variant, forgetting that a negative
mintimes a negative number can become the newmax.
Interview patterns
- Maximum Subarray (Kadane).
- Maximum Product Subarray: carry both the max and min product ending here.
- Maximum Sum Circular Subarray:
max(kadane_max, total - kadane_min), guarding the all-negative case. - Best Time to Buy and Sell Stock: Kadane over daily price differences.
- Coin ChangeIntermediate