Senior 11 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 & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
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
✦ Definition~90s read
What is Two Sum Problem using Hashing?

The Two Sum problem is a canonical coding interview question: given an array of integers and a target sum, find two numbers that add up to the target. The naive brute-force solution checks every pair via nested loops, yielding O(n²) time complexity — fine for small arrays but catastrophic at scale.

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 hashing approach solves this by trading memory for speed: iterate once, storing each number's complement (target minus current value) as a key in a HashMap, with the index as the value. On each subsequent element, check if it exists as a key — if so, you've found your pair in O(1) average time per lookup, dropping overall complexity to O(n).

Where this breaks down is at production scale. With 5 million records, a HashMap storing every element's complement consumes roughly 5 million entries — each entry in Java's HashMap carries ~32 bytes of overhead plus the key and value objects, easily exceeding 500 MB of heap.

The memory blowout occurs because the algorithm stores all elements before finding the answer, and garbage collection pressure spikes as the map resizes. In real-world systems, this is why you'd reach for a sorted array with two-pointer technique (O(n log n) time, O(1) space) or a Bloom filter for approximate membership checks — the HashMap solution optimizes for the wrong axis when data volume is high.

Alternatives exist: if the array is sorted, two pointers give O(n) time with no extra memory. For streaming or memory-constrained environments, a bitset or radix sort can handle integer ranges. The hashing solution is ideal for interviews to demonstrate space-time tradeoffs, but in production you'd benchmark against actual data sizes and memory budgets — 5M records is where the HashMap starts to hurt, and 50M is where it becomes infeasible without distributed memory or external storage.

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.

How HashMap Turns Two Sum Into a Memory Bomb at Scale

The two sum problem asks: given an array of integers and a target, find two numbers that add up to the target. The hashing solution trades space for time: iterate once, for each element compute its complement (target - current), and check if that complement already exists in a HashMap. If yes, return the pair; otherwise store the current element as a key with its index as the value. This gives O(n) time and O(n) space — elegant for small n, but the memory cost grows linearly with input size. At 5 million records, that HashMap holds up to 5 million entries. Each entry in Java carries overhead: an Entry object (~32 bytes), the key and value references, plus the underlying array. Real-world memory blowout happens when the array size exceeds available heap, triggering frequent GC pauses or an OutOfMemoryError. The hashing approach is optimal when n is modest (< 1M) or when you need a single pass. For massive datasets, consider sorting + two-pointer (O(n log n), O(1) space) or streaming with a Bloom filter for approximate membership. The HashMap solution is not a universal hammer — it's a precision tool for bounded input sizes.

Memory ≠ Time Tradeoff
HashMap gives O(n) time but O(n) memory. At 5M records, that's ~200 MB of heap — easily overlooked until production OOM kills the pod.
Production Insight
A real-time fraud detection service ingested 5M transactions per hour and used a HashMap per request to find complementary payment amounts. Each request allocated a fresh map, causing young-gen GC every 2 seconds and full GC every 30 seconds — latency spiked from 10ms to 2s.
The exact symptom: GC logs showed 'Allocation Failure' with 'ParNew' pauses exceeding 500ms, followed by 'ConcurrentMarkSweep' cycles that froze the JVM for seconds.
Rule of thumb: If n > 1M and you don't need the map after the operation, switch to a two-pointer sort or use a primitive int-int map (e.g., Trove, Koloboke) to cut memory by 3x.
Key Takeaway
HashMap two-sum is O(n) time but O(n) memory — at 5M records, that's a heap bomb.
For n > 1M, prefer sorting + two-pointer (O(n log n), O(1) space) or a primitive map to avoid GC thrash.
Always profile memory per entry: Java HashMap overhead is ~32 bytes per entry, not just the key-value pair.
Two Sum Hashing: HashMap Memory Blowout at Scale THECODEFORGE.IO Two Sum Hashing: HashMap Memory Blowout at Scale How HashMap solves O(n²) but risks memory explosion on 5M records Brute Force O(n²) Check all pairs; too slow for large n HashMap Lookup O(n) Store each element's complement in map Memory Accumulation HashMap grows with each insertion 5M Records Blowout HashMap memory exceeds limits Two Pointers (Sorted) O(1) extra space, O(n log n) sort ⚠ HashMap stores all elements before finding pair Use two pointers on sorted input to avoid memory blowout THECODEFORGE.IO
thecodeforge.io
Two Sum Hashing: HashMap Memory Blowout at Scale
Two Sum Problem Hashing

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)

2Sum on Unsorted Input — Where HashMap Actually Earns Its Keep

Most Two Sum variants you'll face in the wild hand you an unsorted array. No ordering, no guarantees. The brute force O(n²) check-every-pair approach works for n=100. For n=10⁶, your interviewer will watch you sweat.

This is where the HashMap stops being a 'trick' and becomes the only sane option. You iterate once, storing each value's index as you go. For every new element, you check if (target - current) exists in the map. That's a single pass — O(n) time, O(n) space.

The why is simple: a HashMap gives you O(1) average lookups. But here's the production reality — hash collisions can degrade that to O(n) worst case. Java 8+ handles this with tree bins when collision lists exceed 8 nodes, but on older JVMs you could eat a performance hit right when your pager goes off.

Unsorted arrays force your hand. No two-pointer shortcuts, no binary search. Your only weapon is the map, and you'd better know how it behaves under load.

TwoSumUnsorted.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — dsa tutorial

import java.util.*;

public class TwoSumUnsorted {
    public static int[] findPair(int[] numbers, int target) {
        Map<Integer, Integer> seen = new HashMap<>();
        for (int i = 0; i < numbers.length; i++) {
            int complement = target - numbers[i];
            if (seen.containsKey(complement)) {
                return new int[]{seen.get(complement), i};
            }
            seen.put(numbers[i], i);
        }
        return new int[]{-1, -1};
    }

    public static void main(String[] args) {
        int[] input = {3, 1, 4, 2, 7};
        int[] result = findPair(input, 6);
        System.out.println("Indices: " + result[0] + ", " + result[1]);
    }
}
Output
Indices: 2, 3
Production Trap: HashMap Collisions Are Not Theoretical
When your array contains millions of integers with poor hash distribution (e.g., sequential IDs), HashMap degrades. Always benchmark with realistic data, not textbook examples.
Key Takeaway
Unsorted input means HashMap is your only O(n) option — but watch for hash collisions in production-scale data.

2Sum on Sorted Input — Two Pointers, Zero Memory Bloat

When the array is sorted, you get to laugh at HashMap users. No extra memory, no hashing overhead, just two pointers dancing toward each other.

The algorithm is painfully simple: start one pointer at index 0, the other at index n-1. Check the sum. If it's too small, move the left pointer right. If it's too large, move the right pointer left. If they match, you win. O(n) time, O(1) space.

This works because sorting imposes a monotonic relationship on the sums. As you slide the left pointer to bigger numbers, sums increase. As you slide the right to smaller numbers, sums decrease. You're effectively performing a binary search on a 2D space without the extra log factor.

Real-world implications: when you control the data pipeline and can guarantee sorted input (e.g., timestamped logs, sorted database result sets), two-pointer beats HashMap every time. No memory spikes, no GC pressure, no collision nightmares. It's the difference between a server that handles 10k req/s and one that OOMs at 2k.

But remember — sorting an unsorted array costs O(n log n). If you're only solving Two Sum once, sorting plus two pointers is slower than a single HashMap pass. Know your constraints before you cargo-cult the solution.

TwoSumSorted.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
// io.thecodeforge — dsa tutorial

import java.util.*;

public class TwoSumSorted {
    public static int[] findPair(int[] sorted, int target) {
        int left = 0, right = sorted.length - 1;
        while (left < right) {
            int sum = sorted[left] + sorted[right];
            if (sum == target) {
                return new int[]{left, right};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return new int[]{-1, -1};
    }

    public static void main(String[] args) {
        int[] input = {1, 2, 3, 4, 7};
        int[] result = findPair(input, 6);
        System.out.println("Indices: " + result[0] + ", " + result[1]);
    }
}
Output
Indices: 1, 3
Senior Shortcut: When to Ignore the HashMap
If your input is already sorted — from a DB query with ORDER BY, a time-series feed, or a monotonic ID sequence — use two pointers. Zero memory allocation, no GC, and it's branch-predictor friendly.
Key Takeaway
Sorted input lets you trade O(n) space for O(1) space with two pointers — always check if you can assume ordering before reaching for a HashMap.

The OG LeetCode Challenge: Cracking Two Sum with Python!

Before you reach for a HashMap, understand why Python dominates beginner solutions: it requires zero memory management. The brute force double loop in Python will pass for small arrays (n<1000) but fails at scale due to Python's interpreter overhead — O(n²) with Python's slow loops means seconds become minutes. The real test is handling 10^4+ elements. Python's dict is a hash table under the hood, exactly like Java's HashMap. The difference? Python's dict operations are optimized in C, so hash collisions are less costly. But the principle is identical: store complement = target - nums[i] as key, index as value. When you encounter nums[j] that equals a stored key, you've solved it in O(n) time, O(n) space. The trap: Python's dict memory overhead is ~72 bytes per entry versus Java's ~48. For 10^6 elements, that's 72MB vs 48MB — significant when interviewer asks 'what if input is 100 million?'

TwoSumPython.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — dsa tutorial

import java.util.*;

class TwoSumPython {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i};
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException();
    }
}
Output
[0, 1] // Example: nums=[2,7,11,15], target=9
Production Trap:
Python's dict accepts unhashable types (lists) silently — Java's HashMap throws NullPointerException on null keys if not handled. Always add null checks when porting Python solutions.
Key Takeaway
HashMap maps complement to index; single pass guarantees O(n) time, O(n) space, but memory per entry varies by language.

🚀 The Game Plan (Approach)

Stop solving Two Sum like you're racing — think in phases. Phase 1: Confirm constraints. Is the array sorted? If yes, two pointers at O(1) space beats any hash map. Phase 2: Assume unsorted input. The HashMap approach: iterate once, store each number's index. For each new number, check if its complement (target - current) already exists in the map. If found, return both indices. Phase 3: Memory optimization. The HashMap stores every element — worst case O(n) memory. But you can reduce memory by early pruning: if target is positive and numbers are positive, skip numbers > target (doesn't work with negatives). Another trick: use array of size max(nums) instead of HashMap if numbers are bounded (e.g., 0 to 10^5). This drops memory to O(1) but only works for small ranges. The game plan forces you to ask: 'What does the input look like?' before coding. Most interviewees code the HashMap instantly — then fail on follow-ups asking for space optimization.

TwoSumGamePlan.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — dsa tutorial

import java.util.*;

class TwoSumGamePlan {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> seen = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int need = target - nums[i];
            if (seen.containsKey(need)) {
                return new int[]{seen.get(need), i};
            }
            seen.put(nums[i], i);
        }
        return new int[]{};
    }
}
Output
[0, 2] // nums=[3,2,4], target=6
Production Trap:
Never insert before lookup — inserting the current element first will find itself if complement equals current value (e.g., target=6, nums=[3,3] returns [0,0], wrong).
Key Takeaway
Check constraints first — sorted array means two pointers; unsorted means HashMap but always insert after lookup to avoid self-match.

💡 Key Takeaways

Mapping values to indices with a HashMap transforms the quadratic Two Sum problem into linear time at the cost of O(n) memory. The core insight is that for each element, you only need to check if its complement (target minus current value) already exists in the map — no nested loops. This works because HashMap provides near-constant-time lookups. The trade-off is memory: storing up to n entries can become problematic at scale, especially in memory-constrained systems. HashMap also handles unsorted input gracefully, which two-pointer or binary search approaches cannot. Always handle duplicates carefully: return the first valid pair or all pairs depending on the problem statement. Edge cases include negative numbers, the target being zero, and large arrays where Integer overflow could occur. For production code, consider using primitive int-to-int maps or sorting-based approaches if memory is more critical than speed. The HashMap solution is your go-to interview answer, but understand the trade-offs before implementing it in high-throughput systems.

TwoSumHashingTakeaway.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — dsa tutorial
import java.util.*;

public class TwoSumHashingTakeaway {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i};
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No solution");
    }
}
Output
Example: nums = [2,7,11,15], target = 9 → [0,1]
Production Trap:
HashMap looks O(1) but hash collisions can degrade to O(n) in worst case. For massive datasets, consider using a high-performance hash function or a sorted array with binary search.
Key Takeaway
HashMap trades linear memory for linear time, making Two Sum O(n) but memory-bound at scale.

🚀 The Game Plan (Approach)

Start by declaring a HashMap to store each element as the key and its index as the value. Iterate through the array exactly once. For each element at index i, compute the complement: target - nums[i]. Check if this complement is already a key in the HashMap. If it exists, you have found your pair — return the index of the complement (stored in the map) and the current index i. If not, store the current element and its index into the map. Continue until you either find a solution or exhaust the array. This single-pass approach ensures you never need to revisit elements, and you avoid the O(n²) cost of comparing every pair. The key decision is checking complements before inserting the current element — this prevents using the same element twice. After the loop, if no pair is found, throw an exception or return a sentinel value per problem constraints. This approach works uniformly for unsorted arrays, handles duplicates by returning the earliest pair, and is the most widely accepted solution in coding interviews. Understand it deeply before exploring variants like multiple pairs or k-sum.

TwoSumGamePlan.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — dsa tutorial
import java.util.*;

public class TwoSumGamePlan {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int comp = target - nums[i];
            if (map.containsKey(comp)) {
                return new int[]{map.get(comp), i};
            }
            map.put(nums[i], i);
        }
        return new int[]{-1, -1};
    }
}
Output
nums = [3,2,4], target = 6 → [1,2] (since 2+4=6)
Pro Tip:
Always check the complement before inserting to avoid using the same index twice. This is the most common subtle bug in implementation.
Key Takeaway
Single-pass check-before-insert strategy ensures O(n) time, avoids self-pairing, and handles all unsorted inputs.
● 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?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Hashing. Mark it forged?

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

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