Longest Subarray with Sum K
Prefix Sum + HashMap Explained
One of the most frequently asked array problems in coding interviews. Beginner-friendly breakdown with Python code, dry run, visualization, and everything you need to crack it in an interview.
To find the longest subarray with sum K, use a prefix sum with a HashMap. As you traverse the array, compute the running sum. If (current_sum − K) exists in the HashMap, update the maximum length. Store the first occurrence of each prefix sum. This gives an O(n) time, O(n) space solution that works even with negative numbers.
👋 Introduction
Imagine you're tracking your monthly expenses. You have a list of daily amounts — some days you spend money, some days you get cashback (negative). At the end, you want to find the longest streak of consecutive days where your net spending was exactly ₹15,000. That's precisely the Longest Subarray with Sum K problem.
This problem shows up at companies like Google, Amazon, and Flipkart. It sounds simple but hides a trap that trips up a huge number of students: they reach for the sliding window technique without thinking, and it breaks entirely when negative numbers are in the array.
In this guide, you'll understand exactly why sliding window fails here, when the prefix sum approach works, and how to implement it cleanly in Python with full confidence.
📋 Problem Statement
Given an array arr[] of integers (which may include negative numbers, zeros, and positives) and an integer k, find the length of the longest subarray whose elements sum to exactly k. Return 0 if no such subarray exists.
Input: arr = [10, 5, 2, 7, 1, 9], k = 15
Output: 4
Why: The subarray [5, 2, 7, 1] (indices 1 to 4) has sum = 15, and it's the longest one with that sum.
Input: arr = [1, -1, 5, -2, 3], k = 3
Output: 4
Why: The subarray [1, -1, 5, -2] (indices 0 to 3) has sum = 3. That's 4 elements — the longest.
📥 Input / Output Format
| Field | Description | Example |
|---|---|---|
| arr | List of integers (can be negative) | [10, 5, 2, 7, 1, 9] |
| k | Target sum (integer) | 15 |
| Output | Length of the longest subarray with sum = k | 4 |
📐 Constraints
- Array can have negative numbers, zeros, and positive numbers
- Array length: 1 ≤ n ≤ 10⁵
- Element values: -10⁴ ≤ arr[i] ≤ 10⁴
- k can be any integer (negative, zero, or positive)
The presence of negative numbers completely changes which algorithm you can use. With only positives, a shrinking window makes sense. With negatives, the sum can go up or down at any step, breaking window-based logic.
🐢 Naive Approach — O(n²)
The brute force idea is simple: try every possible subarray, compute its sum, and track the longest one that equals k.
def longest_subarray_brute(arr, k):
n = len(arr)
max_len = 0
for i in range(n):
curr_sum = 0
for j in range(i, n):
curr_sum += arr[j]
if curr_sum == k:
max_len = max(max_len, j - i + 1)
return max_len
Problem: This is O(n²) time. For an array of 10⁵ elements, that's 10¹⁰ operations — way too slow for any real interview or test.
🚨 Sliding Window DOES NOT Work Here
Many students immediately jump to the sliding window technique because this looks like a "find subarray with target sum" problem. This is one of the most common mistakes in interviews.
Sliding window only works when the array contains strictly non-negative numbers. The logic behind it assumes: if your current window sum exceeds the target, shrinking from the left will reduce the sum. That logic breaks completely when negative numbers are present.
Concrete Failure Example
Consider: arr = [2, -1, 2], k = 3
The correct answer is 3 (entire array: 2 + (-1) + 2 = 3).
With a sliding window: when the sum exceeds k, you shrink from the left. But after removing 2 from the left, your window becomes [-1, 2] with sum = 1. You then expand — missing the correct answer entirely.
Sliding window → only when all elements are non-negative.
Prefix sum + HashMap → works for all cases including negatives.
⚡ Optimal Approach — Prefix Sum + HashMap
The key insight is to use a running prefix sum and store the first time each prefix sum value was seen in a HashMap.
Core Idea
If the sum from index 0 to j is curr_sum, and you want sum from some index i to j to be exactly k, then:
sum(i to j) = curr_sum − prefix[i-1] = k
⟹ prefix[i-1] = curr_sum − k
So at every index, you just check: has the value (curr_sum − k) appeared before as a prefix sum? If yes, you've found a subarray with sum k — and its length is (current_index − index_where_that_prefix_sum_was_seen).
Algorithm Steps
💡 Key Intuition — Why This Works
Why store first occurrence only?
You want the longest subarray. If the same prefix sum appears at index 3 and later at index 7, and you find (curr_sum - k) at a later point, using index 3 gives you a longer subarray than using index 7. So always store the first time a prefix sum appears, and never update it.
Why initialize HashMap with {0: -1}?
If the entire subarray from the beginning (index 0 to j) sums to k, then curr_sum - k = 0. You need 0 to already be in the HashMap, and its index should be -1 so the length calculation j - (-1) = j + 1 works correctly.
Why does it work with negatives?
Unlike sliding window, this approach doesn't try to shrink or grow a window based on the sum direction. It simply computes cumulative sums and looks for a difference — and subtraction works just as well whether numbers are positive or negative.
📊 Prefix Sum Visualization
Let's visualize how the prefix sum and HashMap evolve for arr = [10, 5, 2, 7, 1, 9], k = 15:
Array
Purple cells = the longest subarray [5, 2, 7, 1] with sum 15
Step-by-Step Prefix Sums
| Index | arr[i] | curr_sum | curr_sum−k | In HashMap? | Length | HashMap State |
|---|---|---|---|---|---|---|
| — | — | 0 | — | — | — | {0: -1} |
| 0 | 10 | 10 | -5 | No | — | {0:-1, 10:0} |
| 1 | 5 | 15 | 0 | Yes (at -1) | 1−(−1)=2 | {0:-1, 10:0, 15:1} |
| 2 | 2 | 17 | 2 | No | — | {0:-1,10:0,15:1,17:2} |
| 3 | 7 | 24 | 9 | No | — | {…,24:3} |
| 4 | 1 | 25 | 10 | Yes (at 0) | 4−0=4 ✓ MAX | {…,25:4} |
| 5 | 9 | 34 | 19 | No | — | {…,34:5} |
Answer: 4 (maximum length tracked at index 4)
🔄 Flowchart
HashMap = {0: -1}
Update max_len if longer
already in HashMap
store HashMap[curr_sum] = i
📝 Pseudocode
function longest_subarray_sum_k(arr, k):
hashmap = {0: -1}
curr_sum = 0
max_len = 0
for i from 0 to len(arr) - 1:
curr_sum = curr_sum + arr[i]
if (curr_sum - k) exists in hashmap:
length = i - hashmap[curr_sum - k]
max_len = max(max_len, length)
if curr_sum NOT in hashmap:
hashmap[curr_sum] = i
return max_len
🐍 Python Program
# ============================================================
# Longest Subarray with Sum K
# Approach: Prefix Sum + HashMap | O(n) time, O(n) space
# Author: TechNaga | pro.technaga.com
# ============================================================
def longest_subarray_sum_k(arr, k):
# Store first occurrence of each prefix sum
# {0: -1} handles the case where subarray starts at index 0
prefix_map = {0: -1}
curr_sum = 0 # Running prefix sum
max_len = 0 # Track the longest valid subarray
for i, val in enumerate(arr):
curr_sum += val # Add current element to running sum
# If (curr_sum - k) was seen before,
# then arr[prev_index+1 .. i] has sum = k
if (curr_sum - k) in prefix_map:
length = i - prefix_map[curr_sum - k]
max_len = max(max_len, length)
# Only store first occurrence (for maximum length)
if curr_sum not in prefix_map:
prefix_map[curr_sum] = i
return max_len
# ——— Input Handling ———
if __name__ == "__main__":
arr = list(map(int, input("Enter array elements: ").split()))
k = int(input("Enter target sum k: "))
result = longest_subarray_sum_k(arr, k)
print(f"Longest subarray with sum {k} has length: {result}")
🔬 Dry Run
Input: arr = [10, 5, 2, 7, 1, 9], k = 15
Initial State: prefix_map = {0: -1}, curr_sum = 0, max_len = 0
| i | arr[i] | curr_sum | curr_sum−k | In Map? | Length | max_len | prefix_map after |
|---|---|---|---|---|---|---|---|
| 0 | 10 | 10 | −5 | No | — | 0 | {0:−1, 10:0} |
| 1 | 5 | 15 | 0 | Yes | 1−(−1)=2 | 2 | {…, 15:1} |
| 2 | 2 | 17 | 2 | No | — | 2 | {…, 17:2} |
| 3 | 7 | 24 | 9 | No | — | 2 | {…, 24:3} |
| 4 | 1 | 25 | 10 | Yes | 4−0=4 | 4 | {…, 25:4} |
| 5 | 9 | 34 | 19 | No | — | 4 | {…, 34:5} |
Final Answer: 4 — The subarray [5, 2, 7, 1] (indices 1 to 4) sums to 15.
🔍 Line-by-Line Explanation
📈 Time & Space Complexity
Single pass through the array. HashMap lookups are O(1) average.
The HashMap stores at most n distinct prefix sums.
| Method | Time | Space | Handles Negatives? |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Yes |
| Sliding Window | O(n) | O(1) | No |
| Prefix Sum + HashMap | O(n) | O(n) | Yes ✓ |
🧪 Test Cases
| # | Input | k | Expected | Notes |
|---|---|---|---|---|
| 1 | [10, 5, 2, 7, 1, 9] | 15 | 4 | Standard case |
| 2 | [1, -1, 5, -2, 3] | 3 | 4 | With negatives |
| 3 | [1, 2, 3] | 10 | 0 | No valid subarray |
| 4 | [5] | 5 | 1 | Single element |
| 5 | [0, 0, 0, 0] | 0 | 4 | All zeros |
| 6 | [1, 2, 1, 2, 1, 2] | 3 | 2 | Multiple subarrays, returns longest |
⚠️ Edge Cases
Empty Array
If arr = [], the loop never runs and we return 0. Always safe.
All Zeros, k = 0
Every prefix sum will be 0. Since we store first occurrence only (at -1), every index will trigger a match and the entire array will be the answer. Correct behavior.
Multiple Longest Subarrays
The algorithm naturally handles this. Whichever match gives the larger length wins — all comparisons are done with max(), and we return the final maximum.
k = 0
The algorithm works perfectly. If curr_sum - 0 = curr_sum was seen before, that subarray has sum 0. The initial {0: -1} handles the case where the array from index 0 sums to 0.
🎯 Common Interview Questions
- Why doesn't sliding window work for this problem when negatives are present? — Because shrinking the window doesn't guarantee a smaller sum when negatives exist. The monotonic assumption breaks.
- Why do you initialize the HashMap with {0: -1}? — To handle subarrays that start from index 0. Without it, we'd miss the case where the prefix sum itself equals k.
- Why store only the first occurrence of each prefix sum? — We want the maximum length. Using a later occurrence of the same prefix sum would give a shorter subarray.
- What is the time complexity, and why? — O(n). We make one pass through the array, and each HashMap operation is O(1) average case.
- What happens if the same prefix sum appears multiple times? — We skip updating the HashMap after the first occurrence. The first index gives the longest possible subarray ending at any future index.
- How would you modify this to return the subarray itself instead of just the length? — Track start and end indices: when you find a match, compute start = prefix_map[curr_sum - k] + 1 and end = i. Return arr[start:end+1].
🌍 Real-World Applications
💰 Financial Tracking
Given a record of daily transactions (positive = income, negative = expense), find the longest consecutive period where net cash flow equals a target value. Used in personal finance apps and bank analytics.
⏱️ Time Interval Analysis
In sensor data or server logs, finding the longest time window with a net change of exactly k helps detect patterns in usage, load balancing, and anomaly detection.
📡 Signal Processing
Finding the longest sequence of signal values that sum to a target amplitude is used in audio processing and wireless communications for pattern detection.
❌ Mistakes Beginners Make
- Using sliding window on arrays with negative numbers. It will produce wrong answers without any error — the most dangerous type of bug.
- Not initializing the HashMap with {0: -1}. This causes the algorithm to miss subarrays that start from index 0.
- Overwriting the HashMap when a prefix sum repeats. If you store the latest index instead of the first, you get shorter subarrays — wrong answer for "longest."
- Checking if curr_sum (not curr_sum − k) is in the map. The key check is always curr_sum - k, not curr_sum.
- Computing length as i − prefix_map[curr_sum − k] − 1. The correct formula is i − prefix_map[curr_sum − k] (no −1). The prefix sum at index j was the sum of elements 0..j, so the next element starts at j+1.
⚖️ Approach Comparison
| Approach | Works for Negatives? | Time | Space | Best Used When |
|---|---|---|---|---|
| Brute Force | Yes | O(n²) | O(1) | n ≤ 1000, quick check |
| Sliding Window | No | O(n) | O(1) | Only non-negative arrays |
| Prefix Sum + HashMap | Yes ✓ | O(n) | O(n) | General case — always safe |
✅ Conclusion
Key Takeaways
The Longest Subarray with Sum K is a classic interview problem that tests whether you understand prefix sums deeply. The sliding window approach is a red herring when negative numbers are involved.
The optimal solution using Prefix Sum + HashMap runs in O(n) time, works for all types of arrays, and is built on a simple but powerful mathematical observation: if two prefix sums differ by k, the subarray between them sums to k.
Always initialize the map with {0: -1}, always store only the first occurrence of each prefix sum, and always check for curr_sum − k. Get these three things right and you'll solve this problem every time.
❓ Frequently Asked Questions
Tags