Two Sum Problem Using Hashing — The Fast O(n) Solution Explained
- 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.
- 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. - 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.
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.
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] + "]"); } } }
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.
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] + "]"); } }
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.
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); } }
Step 1: Value=2, Needs=7. Seen? false
Step 2: Value=7, Needs=2. Seen? true
| Aspect | Brute Force (Nested Loops) | Hashing (HashMap) |
|---|---|---|
| Time Complexity | O(n²) — grows with square of input | O(n) — grows linearly with input |
| Space Complexity | O(1) — no extra data structure | O(n) — HashMap stores up to n entries |
| Passes through array | Two nested passes | Single pass |
| Works with duplicates? | Yes | Yes (last index of duplicate is stored) |
| Works with negatives? | Yes | Yes — HashMap handles any integer key |
| Code complexity | Simple to read and write | Slightly more code, but still readable |
| Best use case | Tiny arrays where memory matters | Any real-world input size |
| 10,000 element array | ~50 million comparisons | ~10,000 comparisons |
🎯 Key Takeaways
- 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.
- 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. - 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.
- 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
Interview Questions on This Topic
- QCan 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?
- QWhat happens in your solution if the input array contains duplicate numbers — say [3, 3] with target 6? Does your code handle it correctly, and why? (Answer: Yes, because the check happens before the put, so the second '3' finds the first '3' already in the map).
- QHow would you modify this solution if the array was already sorted? (Follow-up: This is the 'Two Pointers' variant which achieves O(1) space complexity).
- QHow would you handle a scenario where the input array is too large to fit into memory? (Answer: External sorting or MapReduce approach).
Frequently Asked Questions
Why use a HashMap for Two Sum instead of sorting the array?
Sorting + two-pointer is another valid O(n log n) approach, but it destroys the original indices — you'd need extra work to recover them. The HashMap approach is O(n) time, preserves indices naturally, and is generally the preferred answer in interviews unless the problem specifically asks for sorted output or forbids extra memory.
What if there are multiple valid pairs that sum to the target?
The classic Two Sum problem guarantees exactly one solution, but if you need all pairs, switch your result storage from a single return to a List of int arrays. Instead of returning immediately when you find a match, add the pair to the list and keep iterating. The overall O(n) complexity still holds.
Does this solution work if the array contains negative numbers or zeros?
Yes, completely. HashMap keys are just integers — they work with any value including negatives and zero. The complement calculation (target - currentNumber) works correctly with all integers. For example, numbers=[-3, 3] with target=0 correctly finds the pair, as shown in Test Case 4 in the code above.
What is the Big O of HashMap in the worst case?
While average time is $O(1)$, the theoretical worst case is $O(n)$ if many keys hash to the same bucket (collision). However, in modern Java (8+), buckets turn into balanced trees, making the worst case $O(\log n)$.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.