Senior 5 min · March 05, 2026

Two Sum Hashing — HashMap Memory Blowout on 5M Records

HashMap storing 5M entries caused OutOfMemoryError in Java heap space under 256MB.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Two Sum: find two numbers that sum to target
  • Brute force: O(n²) nested loops, works but slows at scale
  • Hashing: single pass, O(n) time, O(n) space
  • Complement = target - current; check if complement already seen
  • Insert after check to prevent self-match
  • Production watch: OOM when no solution exists for huge arrays; memory grows with input
Plain-English First

Imagine you're at a grocery store with a shopping list, and you need to find two items whose prices add up to exactly $10. The slow way is to pick up every item and compare it to every other item — exhausting. The smart way is to glance at an item priced $3, then immediately check a mental sticky note to see if you've already seen a $7 item. That sticky note is a hash map — it lets you look up any past price in an instant rather than scanning the whole store again. That's the entire trick behind the Two Sum hash solution.

The Two Sum problem is probably the most famous coding interview question in the world. It shows up at Google, Amazon, Meta, and practically every tech company that cares about algorithmic thinking. But its fame isn't just because interviewers are lazy — it's because the problem perfectly exposes whether a candidate knows how to trade memory for speed, which is one of the most important skills in software engineering.

The problem itself sounds almost too simple: given a list of numbers and a target value, find the two numbers in the list that add up to the target. The naive approach — check every pair — works, but it's painfully slow for large inputs. The hashing approach cuts that time dramatically by remembering what you've already seen as you walk through the list just once.

By the end of this article you'll understand exactly why hashing makes this problem fast, be able to write the solution from memory, know the exact edge cases that trip up beginners, and have confident answers ready for the three follow-up questions interviewers love to ask.

The Brute Force Approach — And Why It Falls Apart

Before we reach for the elegant solution, let's fully understand the problem and the obvious first attempt. You're given an array of integers — say [2, 7, 11, 15] — and a target number, say 9. Your job is to find which two numbers sum to 9. In this case it's 2 and 7, sitting at index 0 and index 1.

The most natural thing a beginner does is use two nested loops: pick the first number, then loop through every remaining number to see if they sum to the target. This works correctly — but think about what happens when your array has 10,000 elements. For each of the 10,000 numbers, you scan up to 9,999 others. That's roughly 100 million comparisons. On a large dataset this gets brutally slow.

In computer science we describe this cost as $O(n^2)$ — pronounced 'O of n squared'. It means the work grows with the square of the input size. Double the input, quadruple the work. This is the core pain point that hashing solves, and understanding this pain makes the solution feel like a genuine breakthrough rather than a magic trick.

io/thecodeforge/algorithms/TwoSumBruteForce.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package io.thecodeforge.algorithms;

public class TwoSumBruteForce {

    /**
     * Finds the indices of two numbers that add up to the target.
     * Uses two nested loops — simple but slow: O(n^2) time complexity.
     *
     * @param numbers  the input array of integers
     * @param target   the value we want two numbers to sum to
     * @return         an int array with the two indices, or empty array if not found
     */
    public static int[] findTwoSumBruteForce(int[] numbers, int target) {
        // Outer loop: pick each number as the first candidate
        for (int i = 0; i < numbers.length; i++) {
            // Inner loop: compare it against every number that comes after it
            for (int j = i + 1; j < numbers.length; j++) {
                if (numbers[i] + numbers[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[]{};
    }

    public static void main(String[] args) {
        int[] numbers = {2, 7, 11, 15};
        int target = 9;
        int[] result = findTwoSumBruteForce(numbers, target);
        if (result.length == 2) {
            System.out.println("Indices: [" + result[0] + ", " + result[1] + "]");
        }
    }
}
Output
Indices: [0, 1]
Watch Out:
Notice that the inner loop starts at firstIndex + 1, not at 0. Starting at 0 would risk pairing a number with itself — e.g. index 0 with index 0 — which would give wrong answers for inputs like [3, 5] with target 6 (3+3 would falsely match even though there's only one 3 in the array).
Production Insight
In production, O(n²) algorithms become apparent quickly as data grows.
For a 100k array, brute force takes billions of operations, often causing timeout alerts.
Rule: if you see nested loops over data, ask if a hash map can eliminate one loop.
Key Takeaway
Brute force is correct but impractical for large inputs.
Always ask: can we do better than two nested loops?
The answer is often a hash map.

How Hashing Turns an O(n²) Problem Into O(n)

Here's the key insight: when you're standing at any number in the array, you already know exactly what its partner needs to be. If the current number is 7 and the target is 9, the partner must be 2. You don't need to search for it — you just need to check whether you've already seen it.

A HashMap is the perfect tool for this. Think of it as an instant-lookup notebook. As you walk through the array, you write each number and its index into the notebook. When you land on a new number, before writing it down, you flip open the notebook and ask: 'Is the complement (target minus current number) already in here?' If yes — you're done. If no — write the current number down and move on.

This means you only walk through the array once. One pass. The HashMap lookup happens in $O(1)$ — constant time — regardless of how many entries are in the map. So the total work is $O(n)$: you do a fixed amount of work per element, and there are n elements. That's the trade-off: you use a little extra memory (the HashMap) to eliminate the entire inner loop.

This idea — using a data structure to trade memory for speed — is one of the most reusable patterns in all of computer science.

io/thecodeforge/algorithms/TwoSumHashing.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.thecodeforge.algorithms;

import java.util.HashMap;
import java.util.Map;

public class TwoSumHashing {

    /**
     * Finds the indices of two numbers that add up to the target.
     * Uses a HashMap for O(1) average lookups — overall O(n) time complexity.
     *
     * @param numbers  the input array
     * @param target   the target sum
     * @return         indices of the two numbers
     */
    public static int[] findTwoSum(int[] numbers, int target) {
        // Stores { value -> index }
        Map<Integer, Integer> seen = new HashMap<>();

        for (int i = 0; i < numbers.length; i++) {
            int current = numbers[i];
            int complement = target - current;

            // Check if our partner was already encountered
            if (seen.containsKey(complement)) {
                return new int[]{seen.get(complement), i};
            }

            // Store current value and index for future partners to find
            seen.put(current, i);
        }

        return new int[]{}; // No solution found
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int[] result = findTwoSum(nums, 9);
        System.out.println("Found at: [" + result[0] + ", " + result[1] + "]");
    }
}
Output
Found at: [0, 1]
Pro Tip:
Notice we add the current number to the map AFTER checking for its complement — not before. If you insert first, a number could falsely match itself. For example, with numbers=[6] and target=12, inserting 6 first would let it 'find' itself as its own complement. Insert after checking to prevent this.
Production Insight
Always test with negative numbers, zero, and large targets.
A common hidden bug is integer overflow: target - current can overflow for large ints.
Use long for complement calculation in production to avoid silent wraparound.
Key Takeaway
Hashing trades memory for speed.
The complement pattern is reusable across many problems.
Insert after check to avoid self-matching.

What's Actually Happening Inside the HashMap — A Visual Walkthrough

Seeing code is one thing; watching it execute step-by-step is how it really sticks. Let's trace through numbers = [2, 7, 11, 15] with target = 9 manually, as if we're the program.

Step 1: currentNumber = 2. Complement = 9 - 2 = 7. Is 7 in seenNumbers? No — map is empty. Store {2 → 0}. Move on.

Step 2: currentNumber = 7. Complement = 9 - 7 = 2. Is 2 in seenNumbers? YES — it's at index 0! Return [0, 1]. Done.

We never even looked at 11 or 15. The algorithm stopped the moment it found the answer. Now contrast that with brute force, which would have checked every single pair in the worst case.

The HashMap is the reason we can ask 'have I seen X?' and get a yes/no answer almost immediately, without scanning anything. Internally, Java's HashMap converts the key (a number like 2) into a bucket position using a hash function, making retrieval constant-time. You don't need to understand the internal math right now — just trust that containsKey() and get() are both $O(1)$ operations.

io/thecodeforge/algorithms/TwoSumWithTrace.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.thecodeforge.algorithms;

import java.util.HashMap;
import java.util.Map;

public class TwoSumWithTrace {
    public static int[] findTwoSumWithTrace(int[] numbers, int target) {
        Map<Integer, Integer> seenNumbers = new HashMap<>();
        System.out.println("Target: " + target);

        for (int i = 0; i < numbers.length; i++) {
            int num = numbers[i];
            int complement = target - num;
            System.out.printf("Step %d: Value=%d, Needs=%d. Seen? %s\n", 
                              i+1, num, complement, seenNumbers.containsKey(complement));

            if (seenNumbers.containsKey(complement)) {
                return new int[]{seenNumbers.get(complement), i};
            }
            seenNumbers.put(num, i);
        }
        return new int[]{};
    }

    public static void main(String[] args) {
        findTwoSumWithTrace(new int[]{2, 7, 11, 15}, 9);
    }
}
Output
Target: 9
Step 1: Value=2, Needs=7. Seen? false
Step 2: Value=7, Needs=2. Seen? true
Interview Gold:
If an interviewer asks about space complexity, the answer is O(n) — in the worst case (no valid pair exists), you store every single element in the HashMap before giving up. This is the cost you pay for the O(n) time improvement. Always mention both time AND space complexity unprompted — it signals seniority.
Production Insight
The HashMap's O(1) lookup is average case; worst-case O(n) if many collisions.
Use a good hashCode or rely on Java's HashMap improvement (tree buckets after threshold).
But for Two Sum with integers, collisions are rare — the real risk is memory when no solution.
Key Takeaway
Tracing helps verify algorithm correctness.
Understanding O(1) vs O(n) is crucial for system design.
Space trade-off: O(n) memory for O(n) time.

Edge Cases: What Breaks a Naive Solution

The happy path works with [2,7,11,15] and target 9. But what happens when the array is empty? Or has only one element? Or the target is unreachable? A robust solution must handle these gracefully.

  • Empty array: [] — should return empty array immediately, no exceptions.
  • Single element: [5] — no pair, return empty.
  • No solution: [1,2,3] target 7 — returns empty, but the HashMap stores all three entries. Fine for small inputs, but for large datasets without a pair, this is the OOM scenario we saw earlier.
  • Duplicates: [3,3] target 6 — must return [0,1] not [0,0]. Our insert-after-check pattern handles this.
  • Negative numbers: [-3,3] target 0 — complement calculation works.
  • Zero: [0,5] target 5 — complement 5, works.
  • Large target: target = Integer.MAX_VALUE, current = 10, complement = Integer.MAX_VALUE - 10. No overflow if using long.

Always write test cases for these before you start coding in an interview.

io/thecodeforge/algorithms/TwoSumEdgeCases.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.thecodeforge.algorithms;

import java.util.HashMap;
import java.util.Map;

public class TwoSumEdgeCases {

    public static int[] findTwoSum(int[] numbers, int target) {
        if (numbers == null || numbers.length < 2) {
            return new int[]{};
        }
        Map<Integer, Integer> seen = new HashMap<>();
        for (int i = 0; i < numbers.length; i++) {
            // Use long to avoid overflow
            long complement = (long) target - (long) numbers[i];
            int complementInt = (int) complement;
            if (seen.containsKey(complementInt)) {
                return new int[]{seen.get(complementInt), i};
            }
            seen.put(numbers[i], i);
        }
        return new int[]{};
    }
}
Null Check Required:
In production, the input array can be null. Always guard against NullPointerException at the beginning of the method. LeetCode problems usually assume non-null input, but real code must handle it.
Production Insight
In production, assume input can be invalid: empty array should return empty or throw custom exception.
Null array: guard against NullPointerException.
Large target values may overflow int; use long for complement.
Key Takeaway
Edge cases expose robustness.
Always handle null/empty explicitly.
Use long for complement in production.

Variations and Interview Follow-Ups

Interviewers rarely stop at the basic solution. Here are the most common follow-up questions and how to handle them:

  1. Return all pairs that sum to target, not just the first. Instead of returning early, collect each matching pair into a list and keep iterating. Complexity stays O(n) for time, but O(n) for storing all pairs (could be O(n²) pairs in worst case).
  2. Return boolean if a pair exists, not the indices. This simplifies the return type — just return true the moment you find a complement. You can even use a HashSet instead of HashMap since indices aren't needed.
  3. Sorted array variant: Use the two-pointer technique. Sort the array first (O(n log n)), then place one pointer at the start and another at the end. If the sum is too small, move the left pointer right; if too large, move the right pointer left. This achieves O(1) space but O(n log n) time due to sorting.
  4. Multiple queries on the same array: Preprocess by storing all pairs in a HashMap for O(1) lookups later. Building the map costs O(n²), but each query is O(1). Trade-off heavily depends on query-to-array-size ratio.
  5. Three Sum or K Sum: Extend the complement pattern recursively. For Three Sum, fix one number and solve Two Sum for the rest. Complexity becomes O(n²), but the hash pattern still applies.
io/thecodeforge/algorithms/TwoSumTwoPointer.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.thecodeforge.algorithms;

import java.util.Arrays;

public class TwoSumTwoPointer {

    // Works only if array is sorted; otherwise sort first (destroys original indices)
    public static int[] findTwoSumSorted(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum == target) {
                return new int[]{left, right};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return new int[]{};
    }

    public static void main(String[] args) {
        int[] sorted = {2, 7, 11, 15};
        int[] result = findTwoSumSorted(sorted, 9);
        System.out.println("Indices: [" + result[0] + ", " + result[1] + "]");
    }
}
Output
Indices: [0, 1]
Complement Search Mental Model
  • For any current element, the complement is target - current.
  • The hash map provides O(1) lookup for that complement.
  • This model extends to any pair-finding problem: the key is calculating what you're searching for.
  • Two-pointer is a different mental model: narrowing range by sum comparison.
  • Interviewers want to see you pivot between models based on constraints.
Production Insight
Two-pointer is ideal for sorted data streams.
But sorting destroys original indices—recover them via pairing with original positions.
For large data, external sorting may be needed; consider memory constraints.
The HashMap approach is simpler for unsorted data and is the default interview answer.
Key Takeaway
Know multiple solutions; interviewers test adaptability.
Two-pointer: O(n) time, O(1) space but requires sorted input.
HashMap is usually the best answer for unsorted arrays.
Which Approach to Use?
IfArray is unsorted, memory is not an issue
UseUse HashMap (O(n) time, O(n) space)
IfArray is sorted (or can be sorted in place)
UseUse two-pointer (O(n) time, O(1) space)
IfMemory is extremely constrained, unsorted array
UseSort first then two-pointer (O(n log n) time, O(1) space)
IfNeed to answer many queries on the same array
UsePreprocess all pairs into a HashMap (O(n²) prep, O(1) query)
● Production incidentPOST-MORTEMseverity: high

HashMap Memory Blowout: When Two Sum Lost a Production Job

Symptom
OutOfMemoryError in Java heap space after processing 5 million records. The service crashed repeatedly during end-of-month reconciliation.
Assumption
The HashMap solution is O(n) time and O(n) space — both should be fine given the input size and available heap.
Root cause
The input data had no two numbers summing to the target, so the HashMap stored all 5 million entries before returning an empty array. Each entry included an Integer key and value, causing ~64MB overhead plus object metadata. The JVM heap was sized at 256MB, but the combined load of other services pushed it over the limit.
Fix
Added a memory-bound check: if input size exceeds 1 million, switch to a two-pointer approach after sorting (O(1) space). Also increased heap to -Xmx512m and set a timeout on the batch job to fail fast instead of hanging.
Key lesson
  • Always consider worst-case space: if no solution exists, HashMap stores every element.
  • Know your data distribution: if many inputs have no solution, a memory-efficient alternative is safer.
  • Set resource limits and have fallback algorithms for large-scale processing.
Production debug guideSymptom → Action grid for common issues4 entries
Symptom · 01
Returns [0,0] for input [6] and target 12
Fix
Check if you inserted the current number into the map before checking for the complement. Swap the order: check containsKey before put.
Symptom · 02
Fails for duplicates like [3,3] with target 6
Fix
Verify you're not pre-populating the map before the loop. The insert-after-check pattern handles duplicates correctly.
Symptom · 03
Returns empty array when a solution exists
Fix
Check complement calculation for integer overflow. Cast target and current to long before subtraction, or use long type for variables.
Symptom · 04
Performance degrades on large input
Fix
Confirm you're using HashMap, not TreeMap or a nested loop. Profile with JProfiler or use jcmd to inspect GC behavior.
★ Two Sum Hash Solution Quick DebugVerification steps for correctness
Wrong output (indexes don't match expected)
Immediate action
Run the trace version with the sample input [2,7,11,15] and target 9
Commands
java io.thecodeforge.algorithms.TwoSumWithTrace
Check console output shows 'Step 1... Seen? false' then 'Step 2... Seen? true'
Fix now
Ensure containsKey check happens before put; swap if needed.
Memory consumption high during debugging+
Immediate action
Check if input has no solution by printing the size of the map at the end
Commands
jmap -heap <pid>
Analyze heap dump with Eclipse MAT for Integer entry count
Fix now
Switch to two-pointer approach if input is large and memory constrained.
AspectBrute Force (Nested Loops)Hashing (HashMap)
Time ComplexityO(n²) — grows with square of inputO(n) — grows linearly with input
Space ComplexityO(1) — no extra data structureO(n) — HashMap stores up to n entries
Passes through arrayTwo nested passesSingle pass
Works with duplicates?YesYes (last index of duplicate is stored)
Works with negatives?YesYes — HashMap handles any integer key
Code complexitySimple to read and writeSlightly more code, but still readable
Best use caseTiny arrays where memory mattersAny real-world input size
10,000 element array~50 million comparisons~10,000 comparisons

Key takeaways

1
The core insight is complement recognition
instead of searching for a partner, calculate exactly what the partner must be (target - currentNumber), then check if it exists — turning a search into an instant lookup.
2
HashMap's containsKey() and get() both run in O(1) average time, which is what makes the single-pass O(n) solution possible
without that, you'd need a nested loop.
3
Always insert into the map AFTER checking for the complement
this single ordering decision prevents a number from falsely matching itself and correctly handles duplicate values in the array.
4
The Two Sum pattern generalises beyond this exact problem
any time you're searching for a complement, a pair, or a previously-seen value, reach for a HashMap first — it's one of the most reusable patterns in algorithm design.

Common mistakes to avoid

3 patterns
×

Inserting into the map before checking the complement

Symptom
A number can match itself as its own complement. For example, with numbers=[6] and target=12, the number 6 would find itself in the map and incorrectly return [0, 0].
Fix
Always call containsKey() first, then put(). The order is critical.
×

Forgetting that the same number can appear twice in the array

Symptom
For input [3, 3] with target=6, if you pre-populate the map before the loop, the second 3 overwrites the first, losing index 0 and potentially missing the solution.
Fix
Use the insert-after-check pattern. Never pre-populate the map. Build it as you iterate.
×

Returning the values instead of the indices

Symptom
Returns new int[]{complement, currentNumber} instead of new int[]{complementIndex, currentIndex}.
Fix
Always re-read what the problem asks for before writing the return statement. Use clearly named variables like complementIndex so you never confuse a value for an index.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Can you walk me through the time and space complexity of your HashMap so...
Q02SENIOR
What happens in your solution if the input array contains duplicate numb...
Q03SENIOR
How would you modify this solution if the array was already sorted?
Q04SENIOR
How would you handle a scenario where the input array is too large to fi...
Q01 of 04SENIOR

Can you walk me through the time and space complexity of your HashMap solution and explain the trade-off you're making compared to the brute force approach?

ANSWER
The brute force uses two nested loops, giving O(n²) time and O(1) space. The HashMap solution reduces time to O(n) by adding O(n) space. The trade-off is memory for speed: we store each element and its index in a HashMap so we can look up the complement in O(1) average time. For a 10k array, brute force does ~50 million comparisons; hashing does ~10k lookups. The cost is up to O(n) extra memory, which can become a problem if no solution exists.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Why use a HashMap for Two Sum instead of sorting the array?
02
What if there are multiple valid pairs that sum to the target?
03
Does this solution work if the array contains negative numbers or zeros?
04
What is the Big O of HashMap in the worst case?
🔥

That's Hashing. Mark it forged?

5 min read · try the examples if you haven't

Previous
Hash Collisions and Resolution
3 / 11 · Hashing
Next
Group Anagrams Problem