Two Sum in Java DSA: Brute Force to the HashMap One-Pass Trick

  • Last Updated: July 24, 2026
  • By: javahandson
  • Series
img

Two Sum in Java DSA: Brute Force to the HashMap One-Pass Trick

Learn Two Sum in Java DSA step by step. Compare brute force, two-pass, and one-pass HashMap solutions with dry runs, clean code, and complexity analysis.

1. Introduction

Two Sum in Java is the first problem most people meet on LeetCode. It sits at the top of the list for a reason. The problem looks tiny, yet it teaches a habit you will reuse for years.

Here is the task in plain words. You get an array of numbers and one target number. You must find two positions whose values add up to that target. Then you return those two positions as an array.

We build this up slowly. First comes the obvious brute force with two loops. Next we try a two-pass map. Finally we land on the one-pass HashMap that interviewers hope to see.

Every approach gets a full dry run here. We trace the same seven-number array through all three, so you can watch the work shrink from twelve steps down to four.

2. Understanding the Problem

Let us pin down the rules first. A clear statement now saves you from messy bugs later.

  • You get an array of integers, for example [11, 15, 2, 7, 4, 9, 3].
  • A target number comes with it, say 9.
  • Your job is to return the two indices whose values add up to that target.
  • Exactly one valid answer exists, so you never worry about ties.
  • The same element cannot be used twice. Index 0 plus index 0 is not allowed.

For our array with target 9, the answer is [2, 3]. That happens because nums[2] is 2 and nums[3] is 7, and 2 plus 7 equals 9.

Notice we return positions, not the values themselves. Beginners often mix these two up and return [2, 7] by mistake. Read the required output twice before you write code.

2.1 Why This Example Array

Our array is deliberately messy. The values are unsorted, the answer sits in the middle, and a few numbers are larger than the target itself.

  • Numbers 11 and 15 are both bigger than 9, so their partners turn out negative.
  • The answer pair sits at indices 2 and 3, not at the very start or the very end.
  • Values 4, 9 and 3 sit past the answer, so a good solution never even reaches them.

A tidy example like [2, 7] hides all of this. A messy one shows you what actually happens inside the loop.

💡 Interview Insight
Interviewers usually ask whether the array is sorted. If it is, a two-pointer scan works with no extra memory. Asking that one question early shows you probe constraints instead of jumping straight into code.

3. Concepts You Need Here

Two small ideas carry this whole problem. Both come back often in later array questions, so learn them well.

3.1 The Complement

Say the target is 9 and you are standing on the value 2. What partner do you need? You need 7, because 2 plus 7 makes 9.

That partner has a name. We call it the complement, and you find it with simple subtraction.

complement = target - currentNumber
 
target = 9, currentNumber = 2   ->  complement = 7
target = 9, currentNumber = 11  ->  complement = -2
target = 9, currentNumber = 15  ->  complement = -6

This flips the whole problem around. Instead of testing every pair, you ask one focused question at each step. Have I already seen the number I need?

Look at the last two lines above. A value larger than the target gives a negative complement. Such a partner will never exist in an array of positive numbers, and the code handles that on its own.

3.2 The HashMap

A HashMap stores key and value pairs. It answers one question fast: is this key already inside, and what value sits with it?

On average that lookup takes constant time. Size barely matters, so a map with a million entries answers about as fast as one with ten.

Here the key is a number from the array. The value is the index where that number lives. So the map becomes your memory of everything you walked past.

  • Calling map.put(11, 0) records that the value 11 sits at index 0.
  • A call to map.containsKey(7) asks whether the value 7 appeared earlier.
  • Finally map.get(2) hands back the index where the value 2 lives.

4. Approach 1: Brute Force

The first idea most people reach for feels natural. Take every pair of numbers and add them. If a pair hits the target, you found your answer.

4.1 Pseudocode

for i from 0 to n-1:
    for j from i+1 to n-1:
        if nums[i] + nums[j] == target:
            return [i, j]
return []   // no pair found

4.2 Reading the Pseudocode

Picture two fingers resting on the array. Your left finger is i and it holds one number. Your right finger is j and it slides along everything to the right.

  • The outer loop moves the left finger, so i picks the first number of a pair.
  • The inner loop moves the right finger, so j visits every position after i.
  • We start j at i plus 1 for two reasons. It stops a number pairing with itself, and it avoids testing the same pair twice.
  • When the sum matches, we return both positions and stop right away.

Why does j never start at 0? Because pairs like (3, 1) and (1, 3) are the same pair. Starting ahead of i removes that wasted half of the work.

4.3 Java Code

public class TwoSumBrute {
 
    public static int[] twoSum(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) {
                    return new int[] { i, j };
                }
            }
        }
        return new int[] {};
    }
 
    public static void main(String[] args) {
        int[] nums = { 11, 15, 2, 7, 4, 9, 3 };
        int[] answer = twoSum(nums, 9);
        System.out.println(answer[0] + ", " + answer[1]); // 2, 3
    }
}

4.4 Reading the Java Code

Each line here has one clear job. Let us walk through them one at a time.

  • Line 4 starts the outer loop. Variable i walks from index 0 to the last index.
  • Line 5 starts the inner loop. Variable j always begins one step ahead of i.
  • Line 6 adds nums[i] and nums[j], then compares that sum against target.
  • Line 7 builds a small array holding both indices and returns it at once.
  • The return on line 7 exits the whole method, so both loops stop instantly.
  • Line 11 runs only when no pair matched, and it hands back an empty array.

One detail deserves attention. The inner loop restarts for every value of i, and its starting point keeps moving right.

  • With i at 0, j runs across indices 1 through 6, giving six comparisons.
  • Once i moves to 1, j covers indices 2 through 6, giving five comparisons.
  • At i equal to 2, j starts at index 3, and a match ends everything.

This code is easy to read and hard to get wrong. That is its real strength. Speed is the weakness, and we fix it later.

4.5 Dry Run of the Brute Force

Let us trace nums = [11, 15, 2, 7, 4, 9, 3] with target = 9. Watch both pointers move.

Stepi (left index)nums[i]j (right index)nums[j]SumEquals 9?
101111526No
20112213No
30113718No
40114415No
50115920No
60116314No
71152217No
81153722No
91154419No
101155924No
111156318No
1222379Yes. Return [2, 3]

4.6 Reading the Dry Run

Twelve steps ran before the answer appeared. Let us break that down into three clear phases.

  • Steps 1 to 6 keep i at index 0, where the value is 11. Every sum starts at 11, so nothing can reach exactly 9.
  • Steps 7 to 11 keep i at index 1, where the value is 15. That value alone already passes the target, so again no sum works.
  • Step 12 finally moves i to index 2, holding the value 2. Its very first partner is 7, and 2 plus 7 gives 9.

Now look at the wasted effort. The first eleven steps produced nothing useful at all.

  • The loop tested 11 against all six numbers to its right, then gave up on it.
  • The loop tested 15 against all five numbers to its right, then gave up on it.
  • Both values were larger than the target from the start, yet the code never noticed.

Also notice which numbers never got touched as a left value. Indices 4, 5 and 6 hold 4, 9 and 3, and i never reached them. The early return saved that work.

4.7 Time and Space Cost

Brute force tests almost every pair. For an array of size n, that is about n times n over two checks.

  • Time is O(n squared), because the loops nest inside each other.
  • Space is O(1), since we only keep two loop counters.

Our seven numbers needed twelve steps. Double the array to fourteen numbers and the worst case jumps past ninety steps. That growth is the pain point we solve next.

5. Approach 2: The Two-Pass HashMap

Now we bring in the complement idea. Rather than testing pairs, we store every number in a map first. Then we walk the array again and simply ask the map for each partner.

5.1 Pseudocode

map = empty map          // number -> index
 
for i from 0 to n-1:     // pass 1: fill the map
    map[nums[i]] = i
 
for i from 0 to n-1:     // pass 2: look for partners
    need = target - nums[i]
    if need is in map and map[need] != i:
        return [i, map[need]]
return []

5.2 Reading the Pseudocode

Two separate walks happen here. Think of the first walk as taking notes and the second as checking those notes.

  • Pass one records every number with its index, so the map knows where each value lives.
  • Pass two computes the complement for the current number, which we call need.
  • The map lookup asks whether that partner exists anywhere in the array.
  • The guard map[need] != i blocks a number from pairing with itself.

Why is that guard needed here but not in brute force? The map holds the whole array, including the current number. Without the guard, a value of 4 with target 8 would happily match itself.

5.3 Java Code

import java.util.HashMap;
import java.util.Map;
 
public class TwoSumTwoPass {
 
    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
 
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }
 
        for (int i = 0; i < nums.length; i++) {
            int need = target - nums[i];
            if (map.containsKey(need) && map.get(need) != i) {
                return new int[] { i, map.get(need) };
            }
        }
        return new int[] {};
    }
 
    public static void main(String[] args) {
        int[] nums = { 11, 15, 2, 7, 4, 9, 3 };
        int[] answer = twoSum(nums, 9);
        System.out.println(answer[0] + ", " + answer[1]); // 2, 3
    }
}

5.4 Reading the Java Code

The two loops do very different jobs, so we read them separately.

First loop, the note-taking pass:

  • Line 9 walks every index from start to end.
  • Line 10 calls map.put(nums[i], i), storing the number as key and its index as value.
  • After this loop ends, the map holds all seven numbers.

Second loop, the checking pass:

  • Line 14 computes need, which is target minus the current value.
  • Line 15 first asks map.containsKey(need) to see whether that partner exists.
  • The same line then asks map.get(need) != i to reject a self-match.
  • Line 16 returns the current index together with the partner index.

Watch the order of the two conditions on line 15. Java checks the left side first and skips the right side when the left already fails. That short-circuit keeps map.get from running on a key that is missing.

5.5 Dry Run of the Two-Pass Approach

The first pass simply loads the map. Here is what it holds once that loop finishes.

Key (the number)111527493
Value (its index)0123456

Now the second pass starts from index 0 and checks each complement.

Stepi (index)nums[i]need = 9 – nums[i]Is need in map?map.get(need)Result
1011-2Nonot applicableMove on
2115-6Nonot applicableMove on
3227Yes3Match. Return [2, 3]

5.6 Reading the Dry Run

Only three steps ran in the second pass. Compare that with twelve for brute force.

  • Step 1 sits on 11 and needs -2. No negative numbers exist in our array, so the lookup fails.
  • Step 2 sits on 15 and needs -6. That lookup fails for exactly the same reason.
  • Step 3 sits on 2 and needs 7. The map already knows 7 lives at index 3, so we return [2, 3].

Notice what the map did for us at step 3. It answered in one lookup instead of scanning the four remaining numbers.

Still, be fair about the true cost. The first pass touched all seven numbers before the second pass even began. So the work is seven puts plus three lookups, ten operations in total.

💡 Interview Insight
A follow-up you should expect: what if the array holds duplicates, like [3, 3] with target 6? In this two-pass version the map keeps only the last index for a repeated key, so both 3 values collapse to index 1 and the guard rejects the match. Mention that gap out loud, because the one-pass version handles it cleanly.

6. Approach 3: The One-Pass HashMap

Here is the version interviewers really want. The insight is small but powerful. You do not need the whole map before you start looking.

Walk the array once. At each number, first ask the map whether the partner already showed up. If yes, you are done. If not, drop the current number into the map and keep walking.

Think of it as leaving breadcrumbs behind you. Every number you pass leaves a mark, and later numbers only check the trail.

6.1 Pseudocode

map = empty map          // number -> index
 
for i from 0 to n-1:
    need = target - nums[i]
    if need is in map:
        return [map[need], i]
    map[nums[i]] = i
return []

6.2 Reading the Pseudocode

Only one loop runs now. The order of the two steps inside matters a great deal.

  • We compute need first, which is the partner value we are hunting for.
  • Then we check the map, which holds only the numbers to the left of us.
  • A hit means the partner came earlier, so we return its index and the current index.
  • A miss means we store the current number and move on.

Notice why we check before we store. If we stored first, then a target of 8 with a value of 4 would match itself. Checking first removes that bug for free, and it also removes the guard we needed in the two-pass version.

6.3 Java Code

import java.util.HashMap;
import java.util.Map;
 
public class TwoSumOnePass {
 
    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
 
        for (int i = 0; i < nums.length; i++) {
            int need = target - nums[i];
 
            if (map.containsKey(need)) {
                return new int[] { map.get(need), i };
            }
            map.put(nums[i], i);
        }
        return new int[] {};
    }
 
    public static void main(String[] args) {
        int[] nums = { 11, 15, 2, 7, 4, 9, 3 };
        int[] answer = twoSum(nums, 9);
        System.out.println(answer[0] + ", " + answer[1]); // 2, 3
    }
}

6.4 Reading the Java Code Line by Line

This version does the most thinking in the fewest lines, so let us go gently.

  • Line 7 creates an empty map. It starts with nothing and grows as we walk.
  • On line 9 the single loop begins its sweep across every index.
  • Line 10 computes need, holding target minus the current value nums[i].
  • Next, line 12 calls map.containsKey(need) to test for that partner.
  • Line 13 returns map.get(need) first and i second, so the indices come out in order.
  • Storing happens on line 15, which runs only after a failed check.

Two details separate this from the two-pass version.

  • The map never holds the current number when the check runs, so no self-match guard is needed.
  • The map only ever holds numbers to the left, which is exactly the half we care about.

Because a map lookup is constant on average, one sweep is enough. That single idea is what makes this solution shine.

6.5 Dry Run of the One-Pass Approach

Now we trace the same array one final time. Watch the map fill as we go.

Stepi (index)nums[i]need = 9 – nums[i]Is need in map?Map before this stepAction
1011-2No{ } (empty)Put 11 with index 0
2115-6No{ 11=0 }Put 15 with index 1
3227No{ 11=0, 15=1 }Put 2 with index 2
4372Yes, at index 2{ 11=0, 15=1, 2=2 }Return [2, 3]

6.6 Reading the Dry Run

Four steps end the whole thing. Let us look at each one closely.

  • Step 1 sits on 11 and needs -2. The map is empty, so the check fails and we store 11 at index 0.
  • Step 2 sits on 15 and needs -6. The map holds only 11, so again no match, and 15 goes in at index 1.
  • Step 3 sits on 2 and needs 7. The value 7 does exist in the array, but it sits ahead at index 3, so the map has not seen it yet.
  • Step 4 sits on 7 and needs 2. The map stored 2 back at step 3, so the check finally succeeds.

Step 3 is the one worth studying. The partner existed, yet the lookup still failed.

  • At that moment the map only held numbers from indices 0, 1 and 2.
  • The value 7 sits at index 3, which the loop had not reached.
  • So the pair was found later, from the other side, when 7 looked back for 2.

That is the quiet promise of this approach. Every pair gets discovered exactly once, always by its second member looking backward. You never miss a pair, and you never check one twice.

Look at what never happened either. Indices 4, 5 and 6 hold 4, 9 and 3, and the loop never reached them. Three numbers stayed completely untouched.

6.7 Comparing the Three Traces

Same array, same target, three very different amounts of work.

ApproachSteps takenArray elements touchedExtra memory used
Brute force12 comparisonsAll 7, many times overNone
Two-pass HashMap7 puts + 3 lookupsAll 7 in pass oneMap of 7 entries
One-pass HashMap4 stepsOnly the first 4Map of 3 entries

The gap grows fast with size. At seven numbers the difference looks modest. At seven thousand it becomes the difference between instant and unusable.

💡 Interview Insight
Expect this question: why store the number as the key and the index as the value, rather than the other way round? You search by value, never by position. Keys are what you can look up quickly, so the number has to be the key.

7. The Dry Run on Paper

Tables are precise, but a sketch often lands faster. Here is the same one-pass trace drawn by hand.

Pen and paper dry run of the Two Sum one-pass HashMap approach in Java

The array sits along the top with its indices. Each step block on the left shows the current number and its complement. The panel on the right shows the map growing after that step.

Follow the colour cues as you read it.

  • A gold box marks index 2, where the value 2 gets stored for later.
  • The green box marks index 3, where the value 7 finds that stored partner.
  • Down at the bottom, both indices join to form the answer [2, 3].

Some people read pictures faster than tables. Use whichever version clicks for you, or sketch your own on paper while you follow the code.

8. Comparing the Three Approaches

All three give a correct answer. They just pay different prices for it.

ApproachTimeSpaceNote
Brute forceO(n squared)O(1)Simple to write, slow on big arrays
Two-pass HashMapO(n)O(n)Fast, but walks the array twice
One-pass HashMapO(n)O(n)Fastest, single sweep, handles duplicates

In an interview, start with brute force to show you understand the problem. Then bring in the two-pass map to show you spotted the complement trick. Finally tighten it into one pass.

That climb from slow to fast is exactly the story interviewers want to hear. Saying the optimal answer straight away skips the reasoning they came to observe.

💡 Interview Insight
If memory is tight and the array happens to be sorted, mention the two-pointer scan. One pointer starts at the left, another at the right, and you move them based on the sum. That version needs no map at all.

9. Common Mistakes and Edge Cases

A few small traps catch beginners here. Keep them in mind.

  • Returning values instead of indices is the most common slip. The problem wants positions.
  • Storing the number before checking the map breaks the logic. Always check first, then store.
  • Duplicates like [3, 3] with target 6 work fine in the one-pass version. The first 3 lands in the map, and the second one finds it.
  • Negative complements are normal, as our steps 1 and 2 showed. The lookup simply fails and the loop continues.
  • Very large sums can overflow int in other problems. Two Sum keeps values small, so plain int is safe here.

10. Conclusion

Two Sum in Java looks like a warm-up, and in a way it is. But the complement plus HashMap pattern inside it is a real skill.

Our seven-number trace showed the payoff clearly. Brute force needed twelve comparisons. The one-pass map needed four steps and never even looked at the last three numbers.

So take the lesson, not just the answer. Write brute force first when you are stuck. Reach for a map the moment you catch yourself searching for a known value. Once that reflex feels natural, a whole family of problems opens up.

11. Further Reading

12. Interview Questions

Q: What is the Two Sum problem in Java?

A: Two Sum gives you an array of integers and a target number. You must return the indices of the two elements whose values add up to that target. Exactly one valid answer exists, and you cannot use the same element twice.

Q: What is the fastest solution for Two Sum in Java?

A: The one-pass HashMap solution runs in O(n) time and O(n) space. You walk the array once, check whether the complement already sits in the map, and store the current number if it does not.

Q: Why do we check the map before adding the number?

A: If you store the number first, a value that is exactly half the target would match itself. For example, with target 8 and value 4, storing first would wrongly return the same index twice. Checking first prevents that bug.

Q: Does Two Sum in Java work with duplicate numbers?

A: Yes, the one-pass version handles duplicates cleanly. For input [3, 3] with target 6, the first 3 goes into the map at index 0, and the second 3 finds it and returns [0, 1].

Q: What is the time complexity of the brute force Two Sum?

A: Brute force runs in O(n squared) time because it compares every pair of elements using nested loops. It uses only O(1) extra space, which is its single advantage over the HashMap approaches.

Q: Can Two Sum be solved without extra memory?

A: If the array is sorted, you can use two pointers from both ends and move them based on the sum. That approach needs no extra map. On an unsorted array you would have to sort first, which loses the original indices.

javahandson.com | DSA Series | Arrays & Strings

Leave a Comment