title: Maximum Subarray
slug: maximum-subarray
difficulty: medium
leetcode_id: 53
leetcode_url: https://leetcode.com/problems/maximum-subarray/
categories:
  - arrays
  - dynamic-programming
patterns:
  - dynamic-programming

function_signature: "def max_subarray(nums: list[int]) -> int:"

test_cases:
  visible:
    - input: { nums: [-2, 1, -3, 4, -1, 2, 1, -5, 4] }
      expected: 6
    - input: { nums: [1] }
      expected: 1
    - input: { nums: [5, 4, -1, 7, 8] }
      expected: 23
  hidden:
    - input: { nums: [-1] }
      expected: -1
    - input: { nums: [-2, -1] }
      expected: -1
    - input: { nums: [1, 2, 3, 4] }
      expected: 10
    - input: { nums: [-1, -2, -3, -4] }
      expected: -1
    - input: { nums: [3, -2, 5, -1] }
      expected: 6
    - input: { nums: [-2, 1] }
      expected: 1
    - input: { nums: [8, -19, 5, -4, 20] }
      expected: 21

description: |
  Given an integer array `nums`, find the subarray with the largest sum, and return *its sum*.

  A **subarray** is a contiguous non-empty sequence of elements within an array.

constraints: |
  - `1 <= nums.length <= 10^5`
  - `-10^4 <= nums[i] <= 10^4`

examples:
  - input: "nums = [-2,1,-3,4,-1,2,1,-5,4]"
    output: "6"
    explanation: "The subarray [4,-1,2,1] has the largest sum 6."
  - input: "nums = [1]"
    output: "1"
    explanation: "The subarray [1] has the largest sum 1."
  - input: "nums = [5,4,-1,7,8]"
    output: "23"
    explanation: "The entire array [5,4,-1,7,8] has the largest sum 23."

explanation:
  intuition: |
    Imagine you're walking along a number line, collecting values. At each step, you face a simple decision: should you keep accumulating, or cut your losses and start fresh?

    Think of it like this: you're tracking a "running sum" as you traverse the array. If your running sum becomes negative, it's **dragging you down** — any future subarray would be better off starting fresh without that negative baggage.

    The key insight of **Kadane's Algorithm** is that at each position `i`, you have exactly two choices:
    1. **Extend** the previous subarray by adding `nums[i]` (if the previous sum helps)
    2. **Start fresh** at `nums[i]` (if the previous sum was negative)

    Mathematically: `current_sum = max(nums[i], current_sum + nums[i])`

    We also track the maximum sum seen so far, because the optimal subarray might end before the last element.

  approach: |
    We solve this using **Kadane's Algorithm**:

    **Step 1: Initialise tracking variables**

    - `current_sum = nums[0]`: The sum of the current subarray ending at position i
    - `max_sum = nums[0]`: The maximum sum we've found so far
    - Starting with `nums[0]` (not 0) handles all-negative arrays correctly

    &nbsp;

    **Step 2: Iterate from index 1 to the end**

    - For each element `nums[i]`:
      - **Decide**: extend or restart? `current_sum = max(nums[i], current_sum + nums[i])`
      - If `current_sum + nums[i] >= nums[i]`, extend (previous sum is non-negative)
      - If `current_sum + nums[i] < nums[i]`, restart (previous sum was negative)
      - **Update maximum**: `max_sum = max(max_sum, current_sum)`

    &nbsp;

    **Step 3: Return the result**

    - Return `max_sum` — the largest subarray sum encountered
    - Note: we don't return `current_sum` because the optimal subarray might have ended earlier

    &nbsp;

    This elegant algorithm makes the locally optimal choice at each step (extend or restart), which leads to the globally optimal solution.

  common_pitfalls:
    - title: Initialising max_sum to 0
      description: |
        If all elements are negative (e.g., `[-3, -2, -1]`), the maximum subarray sum is `-1`, not `0`.

        Initialising `max_sum = 0` would incorrectly return `0` for such arrays, as if an empty subarray were valid (it's not — the problem requires a non-empty subarray).

        Always initialise with `nums[0]` to guarantee a valid answer.
      wrong_approach: "max_sum = 0"
      correct_approach: "max_sum = nums[0]"

    - title: Only Returning current_sum at the End
      description: |
        The maximum subarray doesn't necessarily include the last element! Consider `[4, -1, 2, 1, -5]`: the maximum sum is `6` (subarray `[4, -1, 2, 1]`), but `current_sum` at the end is `1`.

        You must track `max_sum` throughout the traversal and update it whenever `current_sum` exceeds it.
      wrong_approach: "return current_sum"
      correct_approach: "Track max_sum and return it"

    - title: Confusing Subarray with Subsequence
      description: |
        A **subarray** must be contiguous — you cannot skip elements. This is why Kadane's algorithm works: at each position, the subarray either extends from the previous position or starts fresh.

        A **subsequence** allows skipping elements, which would require a different approach entirely.
      wrong_approach: "Thinking you can skip elements"
      correct_approach: "Subarray = contiguous sequence"

  key_takeaways:
    - "**Kadane's Algorithm**: The classic O(n) solution for maximum subarray — memorise it!"
    - "**Local vs global optimum**: Sometimes making locally optimal choices (extend or restart) yields the global optimum"
    - "**Initialisation matters**: Using `nums[0]` instead of `0` handles edge cases correctly"
    - "**Pattern recognition**: This 'extend or restart' logic applies to many contiguous subarray problems"

  time_complexity: "O(n). Single pass through the array, with O(1) work at each position."
  space_complexity: "O(1). Only two variables (`current_sum` and `max_sum`) regardless of input size."

solutions:
  - approach_name: Kadane's Algorithm
    is_optimal: true
    code: |
      def max_subarray(nums: list[int]) -> int:
          # Initialise with first element (handles all-negative arrays)
          current_sum = nums[0]
          max_sum = nums[0]

          # Process remaining elements
          for i in range(1, len(nums)):
              # Decision: extend previous subarray or start fresh?
              # Extend if previous sum helps, otherwise restart
              current_sum = max(nums[i], current_sum + nums[i])

              # Update maximum if current subarray is better
              max_sum = max(max_sum, current_sum)

          return max_sum
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array.

      **Space Complexity:** O(1) — Only two variables needed.

      At each position, we decide whether to extend the previous subarray or start a new one. We track the maximum sum seen throughout the traversal. This greedy choice at each step produces the globally optimal result.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      def max_subarray(nums: list[int]) -> int:
          n = len(nums)
          max_sum = nums[0]

          # Try every possible starting point
          for i in range(n):
              current_sum = 0
              # Try every possible ending point from i
              for j in range(i, n):
                  current_sum += nums[j]
                  max_sum = max(max_sum, current_sum)

          return max_sum
    explanation: |
      **Time Complexity:** O(n²) — Nested loops checking all subarrays.

      **Space Complexity:** O(1) — Only tracking sums.

      This approach tries every possible subarray by fixing a start index and extending to each possible end index. While correct, it's too slow for large inputs (up to 10 billion operations for n = 10^5).
