Kadane's Algorithm — All-Negative Input Returns $340k Loss
A Kadane's implementation seeded with 0 returns 0 for all-negative arrays, causing a $340k loss.
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
- Core decision: currentSum = max(arr[i], currentSum + arr[i])
- Tracks two values: currentSum (best ending here) and maxSumFound (global best)
- Time: O(n). Space: O(1). Single pass, two variables.
- Seed with arr[0], not 0 — otherwise all-negative arrays return wrong answer.
- Financial analytics: find peak profitability window in time-series data.
- Signal processing: detect strongest signal burst in noisy data.
- Seeding currentSum with 0. Returns 0 for [-3, -1, -2] instead of correct answer -1.
Imagine you're tracking your daily profit and loss at a lemonade stand over a month. Some days you make money, some days you lose it. You want to find the single best streak of consecutive days — the run where your total earnings were the highest. Kadane's Algorithm does exactly that: it walks through the numbers one by one, deciding at each step whether to extend the current streak or start fresh. It's like asking yourself every morning: 'Am I better off adding today to my running total, or cutting my losses and starting over from today?'
Maximum subarray sum — finding the contiguous segment of an array with the largest sum — appears in financial analytics (peak profitability windows), signal processing (strongest signal bursts), and genomics (highest-expression DNA regions). The naive approach checks every start-end pair: O(n²). For a 10,000-element array, that is 50 million operations. Kadane's Algorithm solves it in a single linear pass: O(n), O(1) space.
The algorithm maintains two values: currentSum (the best sum ending at the current position) and maxSumFound (the global best). At each element, it decides: extend the current subarray or start fresh. The decision is greedy and provably correct — a negative prefix can never improve a subarray's sum, so it is always discarded.
The common misconception is that this is 'just a DP problem.' While Kadane's recurrence (dp[i] = max(arr[i], dp[i-1] + arr[i])) is a DP formulation, the O(1) space optimization makes it a distinct pattern. Understanding the greedy intuition — not just the recurrence — is what separates candidates who can explain it on a whiteboard from those who just memorize the code.
Why Kadane's Algorithm Fails on All-Negative Arrays
Kadane's algorithm finds the maximum subarray sum in O(n) time by scanning left to right, maintaining two variables: current sum (the best sum ending at the current position) and global max (the best sum seen so far). At each step, it decides whether to extend the current subarray or start fresh at the current element. The core mechanic is the recurrence: current = max(element, current + element).
In practice, the algorithm works because it exploits the property that any maximum subarray must start at a point where the prefix sum is minimal. It avoids recomputing overlapping subproblems by carrying forward only the useful prefix. The critical nuance: when all inputs are negative, the standard implementation returns the largest negative number (the least loss), not zero. This is correct behavior for the problem as originally defined — maximum subarray sum must be non-empty.
Use Kadane's when you need the maximum sum of any contiguous subarray in a one-dimensional array. Real systems use it for financial time-series anomaly detection, sensor data peak identification, and resource allocation optimization. It's the canonical example of dynamic programming reduced to linear time with constant space — O(1) extra memory.
How Kadane's Algorithm Works — Plain English
Kadane's algorithm finds the maximum-sum contiguous subarray in O(n) time and O(1) space. The key insight: at each position, either extend the previous subarray or start a fresh one — whichever gives a higher sum.
Step-by-step: 1. Initialize current_sum = arr[0], max_sum = arr[0]. 2. For i from 1 to n-1: a. current_sum = max(arr[i], current_sum + arr[i]). b. max_sum = max(max_sum, current_sum). 3. Return max_sum.
Worked example on [-2, 1, -3, 4, -1, 2, 1, -5, 4]: i=0: current=-2, max=-2. i=1: current=max(1,-2+1)=1. max=1. i=2: current=max(-3,1-3)=-2. max=1. i=3: current=max(4,-2+4)=4. max=4. i=4: current=max(-1,4-1)=3. max=4. i=5: current=max(2,3+2)=5. max=5. i=6: current=max(1,5+1)=6. max=6. i=7: current=max(-5,6-5)=1. max=6. i=8: current=max(4,1+4)=5. max=6. Maximum sum subarray = 6, from [4,-1,2,1].
- currentSum > 0: extend. The running sum boosts future elements.
- currentSum <= 0: restart. The running sum is dead weight.
- maxSumFound: tracks the global best across all positions.
- Each element is visited exactly once. O(n) time, O(1) space.
- The algorithm never backtracks. It makes the locally optimal choice at each step.
The Core Intuition — Why the Greedy Decision Works
Before touching code, let's lock in the mental model. At every index i, you face exactly one binary choice: does the current element join the existing subarray, or does it become the start of a brand-new one?
The rule is beautifully simple: if your running sum so far is negative, it's dead weight. Carrying it into the next element can only make that element's contribution worse. So you drop it. You set your running sum equal to the current element and start fresh. If your running sum is positive, it's a booster — keep it.
This is a greedy decision, and it's correct because the optimal subarray can never benefit from a negative prefix. Think about it: if the subarray [3, -5, 2, 4] were the optimal answer, you could always remove the [3, -5] prefix (which sums to -2) and get [2, 4] with a larger sum. That contradiction proves negative prefixes can never be part of the true maximum subarray.
The algorithm tracks two values at all times: currentSum (the best sum ending at the current position) and maxSumFound (the global best seen so far). Every step, you update both. When the loop finishes, maxSumFound is your answer.
- Seed with arr[0]: correct for all-negative, all-positive, and mixed arrays.
- Seed with 0: returns 0 for all-negative arrays. Wrong answer, no error.
- If the problem allows empty subarrays: seed with 0 is correct. Read the problem statement.
- If the problem requires non-empty subarrays: seed with arr[0]. This is the default.
- Test with [-3, -1, -2] to verify. Expected: -1. If you get 0, you seeded wrong.
Tracking the Actual Subarray — Not Just the Sum
The basic version tells you the maximum sum, but real-world use cases often need to know which subarray produced it. Imagine a financial dashboard that needs to highlight the exact date range of peak profitability — the number alone isn't enough.
Extending Kadane's to track indices takes almost no extra code. You record a potentialStart index whenever you decide to restart (i.e., when starting fresh beats extending). The moment that fresh start leads to a new global maximum, you commit it: you update bestStart, bestEnd, and maxSumFound together as an atomic snapshot.
This is a subtle but important pattern. You only want to commit the start index when you've confirmed it produced a better result, not optimistically when you reset. That distinction separates a correct implementation from a buggy one that snaps the wrong start index.
- potentialStart: updated on every reset. Tentative — may never become the answer.
- bestStart: updated only when currentSum > maxSumFound. Confirmed winner.
- Premature commit: setting bestStart = potentialStart on reset gives wrong boundaries.
- Lazy commit: setting bestStart = potentialStart only inside the max-update block is correct.
- Test with [1, 2, -10, 3, 7, -1, 8]. Expected: start=3, end=6, sum=17.
Kadane's vs. Brute Force vs. Divide and Conquer — Choosing Wisely
Understanding why Kadane's is the right choice requires knowing what you're giving up and getting compared to the alternatives.
The brute-force approach uses two nested loops to check every (start, end) pair. It's O(n²) and straightforward to reason about, which makes it fine for arrays under a few hundred elements in non-critical paths. Divide and Conquer solves the problem in O(n log n) — better than brute force, but still worse than Kadane's. Its advantage is that it's parallelizable: you can split the array across multiple cores and merge results, which matters in distributed systems processing massive datasets.
Kadane's wins on pure time complexity and simplicity for the single-machine case. One pass, two variables, done. The only scenario where you'd reach for Divide and Conquer over Kadane's is when you need to run this computation across distributed partitions where a central linear scan isn't possible — think MapReduce jobs over terabyte-scale log files.
For coding interviews, Kadane's is the expected answer the moment you see 'maximum subarray.' Mentioning the Divide and Conquer alternative shows depth and will genuinely impress a senior interviewer.
- Brute force: O(n^2). Simple. Fine for arrays under 1,000 elements.
- Divide and conquer: O(n log n). Parallelizable. Best for distributed systems.
- Kadane's: O(n). Simple. Best for single-machine linear scans.
- Interview expected answer: Kadane's. Mention D&C for bonus points.
- D&C advantage: split array across machines, compute max in each partition, merge by checking the crossing subarray.
The Naive Approach: Why You'd Never Ship It (But Should Still Understand)
Before you blindly trust Kadane, you need to know what you're replacing. The brute-force solution iterates over every possible subarray using two nested loops. Outer loop picks the start index. Inner loop expands to the end, accumulating sum and tracking the maximum. Time complexity: O(n²). Space: O(1). That's fine for a toy array of 100 elements. For a production data pipeline ingesting 10 million sensor readings? Your service will timeout before you get your first cup of coffee.
The WHY is simple: this approach doesn't exploit any structure of the problem. It's the equivalent of checking every drawer in a house to find a key when you know it's probably in the kitchen. It works, but it's embarrassingly inefficient. You'll see this pattern in junior code reviews — they solve the problem, but they don't understand the cost. Understanding the naive approach is critical because it frames why Kadane's linear scan is not just clever, but necessary for any system that needs to scale.
Kadane's Algorithm: The O(n) Solution That Actually Ships
Kadane's Algorithm is the production-grade solution. It's a single pass, O(n) time, O(1) space. The core insight: you carry forward a running sum (currentSum) only if it helps you. At each element, you decide: do I extend the existing subarray, or do I start fresh from this element? The decision is greedy — if currentSum + nums[i] is greater than nums[i], you extend. Otherwise, you reset. You then track the maximum of currentSum across all iterations.
Why does this work? Because any subarray that drags the sum down below the current element is poison. If the running sum ever goes negative, it can only reduce the potential of future elements. So you drop it. This is the exact same reasoning behind why you'd reject a team member who constantly derails progress — cut losses early. The beauty is that this single decision handles all cases, including the all-negative arrays (the current element itself becomes the new candidate). One pass, no backtracking. That's how you write code that survives production.
max calls, zero confusion. Any solution slower than O(n) is a code smell.Trading Algorithm Identified Wrong Peak Window: Seeded with Zero on All-Negative Returns
- Seeding Kadane's with 0 silently returns wrong answers on all-negative arrays. No exception, no error — just a wrong number that downstream systems trust.
- Always validate algorithm outputs against known edge cases. A 0 result on an all-negative input is a red flag.
- Financial systems must reject empty-subarray results. Add explicit validation: maxSumFound must correspond to a non-empty subarray.
- Unit tests for Kadane's must include: all-negative, single element, mixed, and all-positive arrays.
- Document the seeding requirement in code comments. Future developers will not know why arr[0] is used instead of 0.
Print initial values: System.out.println("currentSum=" + currentSum + " maxSumFound=" + maxSumFound)Run with input [-3, -1, -2] — expected answer: -1Key takeaways
currentSum = Math.max(element, currentSum + element) — this single line encodes the entire greedy insight that a negative prefix can never help you.Practice These on LeetCode
Interview Questions on This Topic
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
That's Arrays & Strings. Mark it forged?
6 min read · try the examples if you haven't