Maximum Subarray in Java DSA: Brute Force to Kadane’s Algorithm

  • Last Updated: July 13, 2026
  • By: javahandson
  • Series
img

Maximum Subarray in Java DSA: Brute Force to Kadane’s Algorithm

Learn the Maximum Subarray in Java DSA with a clear walkthrough: brute force plus Kadane’s Algorithm, dry run, and complexity comparison.

1. Introduction

The Maximum Subarray problem in Java is a small question with a big payoff. It looks tricky at first. But once you meet Kadane’s Algorithm, the whole thing clicks, and you carry that trick into many harder DSA problems.

Here is the task in plain words. You get an array of numbers, and some may be negative. You must find the contiguous slice with the largest sum. The slice has to be one unbroken run of elements, so you cannot skip around.

In our last article we solved Contains Duplicate with a HashSet. Now we shift from checking membership to tracking a running total. In this guide we start with brute force, then trim it down, and finally reach Kadane’s clean one-pass solution. We also dry run the code by hand so you can watch the numbers move.

2. Understanding the Problem

Let us pin down the rules first. A clear problem statement keeps you out of trouble later.

  • You get an array of integers, for example [-2, 1, -3, 4, -1, 2, 1].
  • You must return the largest sum of any contiguous subarray.
  • The subarray must have at least one element, so an empty slice is not allowed.
  • Values can be negative, positive, or zero, which is what makes this fun.

For that sample array, the answer is 6. It comes from the slice [4, -1, 2, 1]. Notice we return the sum here, not the slice itself. Many beginners start hunting for start and end indices, but the classic version only wants the value.

💡 Interview Insight
Interviewers often ask why an all-negative array like [-3, -1, -2] still has an answer. Because a subarray needs at least one element, the best you can do is the single largest value, which is -1. Naming this edge case early shows you read the constraints carefully.

3. Concepts You Need Here

Two small ideas carry this whole problem. Both are easy, and both show up again in later array questions.

3.1 Running Sum

A running sum is just a total you keep updating as you walk the array. At each number you make one choice: extend the current run, or start fresh from here. That single decision is the heart of the whole solution.

3.2 Tracking a Best-So-Far

The second idea is even simpler. While the running sum moves up and down, you keep a separate variable that remembers the best total you have ever seen. So even if the run dips later, your best value stays safe.

4. Approach 1: Brute Force

The first idea most people reach for is direct. Try every possible subarray, add up each one, and keep the largest sum you find.

4.1 Pseudocode

best = -infinity
for start from 0 to n-1:
    sum = 0
    for end from start to n-1:
        sum = sum + nums[end]
        best = max(best, sum)
return best

The outer loop fixes a starting point. The inner loop stretches the slice one element at a time and updates the running sum. We compare that sum against our best after each step.

4.2 Java Code

public class MaximumSubarrayBrute {
    public static int maxSubArray(int[] nums) {
        int best = Integer.MIN_VALUE;
        for (int start = 0; start < nums.length; start++) {
            int sum = 0;
            for (int end = start; end < nums.length; end++) {
                sum += nums[end];
                best = Math.max(best, sum);
            }
        }
        return best;
    }
 
    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // 6
    }
}

4.3 Why This Works, Step by Step

Let us go slow and picture what the code is really doing. A subarray is just a slice of the array with a left edge and a right edge. Brute force says: try every left edge, and for each one, try every right edge. That covers all possible slices.

Think of it like this. You put your left finger on one number. Then you drag your right finger across the array, adding up numbers as you go. Every time you move the right finger, you have a new slice, so you check its total.

Now let us tie each line to that picture.

  • The outer loop with start is your left finger. It sits on one number and stays there while the inner loop runs. When the inner loop finishes, start moves one step right and we begin again.
  • The line int sum = 0 resets the total every time the left finger moves. This matters. Each new left edge begins a brand new slice, so the old total must be wiped clean.
  • The inner loop with end is your right finger. It starts on the same spot as start, then walks rightward to the end of the array.
  • The line sum += nums[end] adds the number under the right finger. Because we never reset sum inside the inner loop, it grows to hold the total of the whole slice from start to end.
  • The line best = Math.max(best, sum) compares the current slice total against the best we have seen. If this slice is bigger, best takes its value. If not, best stays the same.
  • When both loops finish, every slice has been tried and best holds the winner. We return it.

Here is the key idea that trips people up. We do not build a fresh sum from scratch for every slice. Instead, as the right finger moves one step, we just add one more number to the total we already had. That small reuse saves a lot of repeated adding.

This version is easy to trust because it literally checks everything. Nothing is skipped, so it cannot miss the answer. Its only weakness is speed, which we fix next.

4.4 Time and Space Cost

Let us count the work in plain terms. The left finger can sit on any of the n numbers. For each of those spots, the right finger walks across the rest of the array, which is again up to n steps. So the total work is roughly n times n.

In Big-O terms, that is O(n squared) time. If the array has 10 numbers, that is around 100 steps. If it has 1000 numbers, it jumps to around a million steps. You can see how fast this grows, which is why brute force drags on big inputs.

The space cost is much friendlier. We only keep two extra variables, sum and best, no matter how large the array is. So the space is O(1), meaning constant. For small arrays this whole approach is perfectly fine. For large ones, we need something faster.

5. Approach 2: Kadane’s Algorithm

Now for the version interviewers really want. Kadane’s Algorithm walks the array just once. Instead of rebuilding every slice, it carries one running total and makes a smart choice at each number. To really get it, let us build up to the final code in two steps.

5.1 The Core Idea

Imagine you are walking along the array, adding numbers to a running total as you go. At some point that total might dip below zero. When it does, dragging it forward only hurts you. A negative total can never help a future slice, so you may as well throw it away and start again.

Here is a money analogy that makes it click. Say you are saving money each day, but some days you lose money. If your savings ever go negative, it is smarter to reset to zero and start saving again, rather than carry that debt forward. Kadane’s does exactly this with array sums.

So the plan has two moving parts. We keep a running total for the slice we are building, and we reset it to zero the moment it turns negative. Alongside that, we keep a separate best value that remembers the highest total we have ever reached.

5.2 First Version: Reset When the Total Goes Negative

Let us write that plan out literally, with a plain if check for the reset. This version matches the analogy word for word, so it is the easiest one to read.

public class MaximumSubarrayReset {
    public static int maxSubArray(int[] nums) {
        int currentSum = 0;
        int bestMax = nums[0];
        for (int i = 0; i < nums.length; i++) {
            currentSum += nums[i];         // add the current number
            bestMax = Math.max(bestMax, currentSum);
            if (currentSum < 0) {          // total turned negative
                currentSum = 0;            // throw it away, start fresh
            }
        }
        return bestMax;
    }
 
    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // 6
    }
}

Read it slowly and it mirrors the savings story exactly.

  • currentSum is our running total for the slice we are building. It starts at 0, like an empty piggy bank.
  • bestMax remembers the highest total we have seen. We seed it with nums[0] so an all-negative array still works, which we explain below.
  • currentSum += nums[i] adds the current number to the running total.
  • bestMax = Math.max(bestMax, currentSum) records this total if it beats our record. We check this before the reset, so a single positive spike is never missed.
  • if (currentSum < 0) { currentSum = 0; } is the reset. The instant our total drops below zero, we drop it and start the next slice fresh from zero.

The order of the last two steps matters. We record the best first, then reset. If you reset before recording, you could wipe out a total you never got credit for. So always update bestMax, then check for the reset.

5.3 Dry Run of the Reset Version

Let us trace this version by hand on nums = [-2, 1, -3, 4, -1, 2, 1]. Watch currentSum climb, then snap back to zero whenever it turns negative. We start with currentSum = 0 and bestMax = -2.

inumcurrentSum += numbestMaxNegative? Reset?
0-20 + -2 = -2-2Yes, reset to 0
110 + 1 = 11No
2-31 + -3 = -21Yes, reset to 0
340 + 4 = 44No
4-14 + -1 = 34No
523 + 2 = 55No
615 + 1 = 66No

Notice step 2. The total drops to -2, so we reset to zero right after recording. That reset is why step 3 starts clean at 4 instead of dragging the -2 forward. From there the run climbs to 6, which is our answer.

5.4 Optimizing to Kadane’s Compact Form

The reset version is clear, but Kadane’s classic form folds that if check into a single line. Watch how the two ideas are really the same.

In the reset version, when currentSum is negative we throw it away, so the next number starts a run of just itself. When currentSum is positive, we keep it and add the next number on top. That is exactly a choice between two values: the number alone, or the number plus the running total.

We can write that choice in one line with Math.max. If the running total was negative, currentMax + nums[i] would be smaller than nums[i] alone, so Math.max picks nums[i] and effectively resets. If the running total was positive, currentMax + nums[i] wins, so we extend. The if check disappears into the max.

// The reset logic ...
currentSum += nums[i];
if (currentSum < 0) currentSum = 0;   // reset when negative
 
// ... becomes this single line:
currentMax = Math.max(nums[i], currentMax + nums[i]);

Both do the same job. The one-liner just decides restart-or-extend in one comparison, instead of adding first and cleaning up after. Here is the full compact version.

public class MaximumSubarrayKadane {
    public static int maxSubArray(int[] nums) {
        int currentMax = nums[0];
        int bestMax = nums[0];
        for (int i = 1; i < nums.length; i++) {
            currentMax = Math.max(nums[i], currentMax + nums[i]);
            bestMax = Math.max(bestMax, currentMax);
        }
        return bestMax;
    }
 
    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1 };
        System.out.println(maxSubArray(nums)); // 6
    }
}

5.5 Reading the Compact Code Line by Line

Now let us walk through the final version slowly. We keep two variables, and it helps to know the job of each one before we start.

  • currentMax is the best total for a run that ends right here, at the current number. It changes at every step.
  • bestMax is the best total we have seen anywhere in the whole array so far. It only changes when we beat our record.

With those two roles clear, the lines make sense.

  • int currentMax = nums[0] and int bestMax = nums[0]. We seed both with the first element. This handles the very first number cleanly, so we never treat the array as empty. It also means a one-element array just returns that element.
  • for (int i = 1; i < nums.length; i++). We start the loop at index 1, not 0, because the first number is already baked into both variables. There is no point in processing it twice.
  • currentMax = Math.max(nums[i], currentMax + nums[i]). This is the heart of the whole algorithm. It compares two choices side by side. One choice, nums[i], means start a fresh run from this number alone, which is the reset in disguise. The other choice, currentMax + nums[i], means extend the old run by adding this number. Math.max picks whichever gives the larger total.
  • bestMax = Math.max(bestMax, currentMax). After we settle the current run, we check it against our all-time record. If the current run just beat the record, bestMax updates. If not, bestMax quietly stays where it was.
  • return bestMax. After one full pass, bestMax holds the largest total any run ever reached. That is our answer.

Notice why we need two separate variables and not one. The current run can rise, fall, and even reset to a small number. If we tracked only that, we would lose the big total we hit earlier. bestMax acts like a photograph of our best moment, safe from whatever the run does later.

Because we touch each number exactly once and do only a couple of comparisons per number, the work grows in a straight line with the array size. So the time is O(n) and the space is O(1). That single clean pass is what makes Kadane’s Algorithm shine.

💡 Interview Insight
A common follow-up asks why we seed with nums[0] instead of 0. If you start best at 0, an all-negative array wrongly returns 0, since no run can beat an empty total. Seeding with the first element keeps the answer correct for every input.

6. Dry Run of the Compact Version

Now let us trace the compact Kadane’s version on the same array, nums = [-2, 1, -3, 4, -1, 2, 1]. It should reach the same answer as the reset version, just through the Math.max line instead of an if check. We follow both currentMax and bestMax as they change.

inumcurrentMax = max(num, prev + num)bestMax
0-2-2 (start)-2
11max(1, -2 + 1) = 11
2-3max(-3, 1 + -3) = -21
34max(4, -2 + 4) = 44
4-1max(-1, 4 + -1) = 34
52max(2, 3 + 2) = 55
61max(1, 5 + 1) = 66

Look at step 3 closely. The running total had dipped to -2, so adding 4 gives only 2. But 4 by itself is larger, so we restart the run at 4. Compare this to the reset version: there, currentSum had already snapped to 0, so 0 + 4 also gave 4. Both land on the same value, which shows the two versions really are the same idea.

From there the run climbs to 6, which becomes our final answer.

6.1 The Same Dry Run on Paper

Kadane's maximum subarray

The sketch shows the same trace. The array sits on top, each step lists the choice, and the running values sit on the right. Many people find this picture easier than the table, so use whichever clicks for you.

7. Comparing the Approaches

Both approaches give the correct answer. They just pay different prices. Here is the plain comparison.

ApproachTimeSpaceNote
Brute forceO(n squared)O(1)Simple but slow on big inputs
Kadane’sO(n)O(1)Fastest, single pass, no extra memory

In an interview, mention brute force first to show you understand the problem. Then move to Kadane’s to show you can reach the best time complexity. That jump from slow to fast is exactly what interviewers want to see. Notice Kadane’s even wins on space, since it needs no extra array.

💡 Interview Insight
If the interviewer asks you to also return the subarray itself, not just the sum, track the start and end indices alongside currentMax. Reset the tentative start whenever you restart the run. Mentioning this extension out loud signals real depth.

8. Common Mistakes and Edge Cases

A few small traps catch beginners on this problem. Keep these in mind.

  • Seeding best at 0 breaks all-negative arrays. Always seed with the first element instead.
  • A single-element array is valid, and the answer is simply that element.
  • Do not confuse this with subsequence problems. The slice here must be contiguous, so you cannot skip elements.
  • Watch for integer overflow only on very large inputs. For interview-sized arrays, a plain int is fine.

9. Interview Questions

Q: What is the Maximum Subarray problem in Java?

A: You are given an array of integers, and some may be negative. The task is to find the contiguous subarray with the largest sum and return that sum. The slice must be one unbroken run, so you cannot skip elements.

Q: How does Kadane’s Algorithm solve Maximum Subarray?

A: Kadane’s walks the array once. At each number it picks the larger of the number alone or the number added to the running total, then updates a best-so-far value. This gives an O(n) time and O(1) space solution.

Q: What is the time complexity of Kadane’s Algorithm?

A: Kadane’s Algorithm runs in O(n) time because it makes a single pass through the array. It uses O(1) extra space, since it only tracks two running values.

Q: Why seed the answer with the first element instead of zero?

A: Seeding with zero breaks all-negative arrays, since no run can beat an empty total, so the code wrongly returns zero. Seeding with the first element keeps the result correct for every input.

Q: Can Maximum Subarray also return the subarray itself?

A: Yes. Track a tentative start index and reset it whenever the run restarts, then record start and end whenever bestMax improves. This returns the actual slice alongside the sum.

10. Conclusion

The Maximum Subarray problem in Java looks small, and it is. But Kadane’s Algorithm inside it is a real skill. You will reuse that extend-or-restart idea in many tougher array and dynamic programming problems.

So take the pattern, not just the answer. Try brute force first to be safe. Then reach for Kadane’s when you want a fast single pass. Once that decision feels natural, a whole family of problems opens up for you.

11. What’s Next

You now have Kadane’s Algorithm in your toolkit, so let us keep the momentum going. Here is where you came from and where you head next in this series.

  • Previous: Contains Duplicate in Java — use a HashSet to spot a repeat in one pass.
  • Next up: Move Zeroes — shift every zero to the end while keeping the other values in order.

12. Further Reading

Leave a Comment