Skip to content
Home DSA Two Sum Problem Using Hashing — The Fast O(n) Solution Explained

Two Sum Problem Using Hashing — The Fast O(n) Solution Explained

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Hashing → Topic 3 of 11
Two Sum problem using hashing explained from scratch.
🧑‍💻 Beginner-friendly — no prior DSA experience needed
In this tutorial, you'll learn
Two Sum problem using hashing explained from scratch.
  • 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.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

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.java · JAVA
12345678910111213141516171819202122232425262728293031323334
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).

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.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041
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.

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.java · JAVA
12345678910111213141516171819202122232425262728
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.
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

  • 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

    Inserting into the map before checking the complement — If you call seenNumbers.put(currentNumber, currentIndex) before seenNumbers.containsKey(complement), 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 check containsKey first, then put.
    Fix

    always check containsKey first, then put.

    Forgetting that the same number can appear twice in the array — For input [3, 3] with target=6, the correct answer is indices [0, 1]. The insert-after-check pattern handles this correctly: when you reach the second 3, the first 3 is already in the map, so the complement check succeeds. But if you pre-populate the map before the loop (a common shortcut attempt), the second 3 would overwrite the first, and you'd lose index 0. Never pre-populate — build the map as you go.

    as you go.

    Returning the values instead of the indices — The Two Sum problem (in its classic LeetCode form) asks for indices, not the numbers themselves. A common mistake is returning new int[]{complement, currentNumber} instead of new int[]{complementIndex, currentIndex}. Always re-read what the problem asks for before writing the return statement, and use clearly named variables like complementIndex so you never confuse a value for an index.

    r an index.

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)$.

🔥
Naren Founder & Author

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.

← PreviousHash Collisions and ResolutionNext →Group Anagrams Problem
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged