Two Sum in Java DSA: Brute Force to the HashMap One-Pass Trick
-
Last Updated: July 24, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
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.
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.
Let us pin down the rules first. A clear statement now saves you from messy bugs later.
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.
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.
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. |
Two small ideas carry this whole problem. Both come back often in later array questions, so learn them well.
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.
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.
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.
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 foundPicture 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.
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.
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
}
}Each line here has one clear job. Let us walk through them one at a time.
One detail deserves attention. The inner loop restarts for every value of i, and its starting point keeps moving right.
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.
Let us trace nums = [11, 15, 2, 7, 4, 9, 3] with target = 9. Watch both pointers move.
| Step | i (left index) | nums[i] | j (right index) | nums[j] | Sum | Equals 9? |
|---|---|---|---|---|---|---|
| 1 | 0 | 11 | 1 | 15 | 26 | No |
| 2 | 0 | 11 | 2 | 2 | 13 | No |
| 3 | 0 | 11 | 3 | 7 | 18 | No |
| 4 | 0 | 11 | 4 | 4 | 15 | No |
| 5 | 0 | 11 | 5 | 9 | 20 | No |
| 6 | 0 | 11 | 6 | 3 | 14 | No |
| 7 | 1 | 15 | 2 | 2 | 17 | No |
| 8 | 1 | 15 | 3 | 7 | 22 | No |
| 9 | 1 | 15 | 4 | 4 | 19 | No |
| 10 | 1 | 15 | 5 | 9 | 24 | No |
| 11 | 1 | 15 | 6 | 3 | 18 | No |
| 12 | 2 | 2 | 3 | 7 | 9 | Yes. Return [2, 3] |
Twelve steps ran before the answer appeared. Let us break that down into three clear phases.
Now look at the wasted effort. The first eleven steps produced nothing useful at all.
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.
Brute force tests almost every pair. For an array of size n, that is about n times n over two checks.
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.
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.
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 []Two separate walks happen here. Think of the first walk as taking notes and the second as checking those notes.
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.
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
}
}The two loops do very different jobs, so we read them separately.
First loop, the note-taking pass:
Second loop, the checking pass:
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.
The first pass simply loads the map. Here is what it holds once that loop finishes.
| Key (the number) | 11 | 15 | 2 | 7 | 4 | 9 | 3 |
|---|---|---|---|---|---|---|---|
| Value (its index) | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
Now the second pass starts from index 0 and checks each complement.
| Step | i (index) | nums[i] | need = 9 – nums[i] | Is need in map? | map.get(need) | Result |
|---|---|---|---|---|---|---|
| 1 | 0 | 11 | -2 | No | not applicable | Move on |
| 2 | 1 | 15 | -6 | No | not applicable | Move on |
| 3 | 2 | 2 | 7 | Yes | 3 | Match. Return [2, 3] |
Only three steps ran in the second pass. Compare that with twelve for brute force.
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. |
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.
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 []Only one loop runs now. The order of the two steps inside matters a great deal.
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.
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
}
}This version does the most thinking in the fewest lines, so let us go gently.
Two details separate this from the two-pass version.
Because a map lookup is constant on average, one sweep is enough. That single idea is what makes this solution shine.
Now we trace the same array one final time. Watch the map fill as we go.
| Step | i (index) | nums[i] | need = 9 – nums[i] | Is need in map? | Map before this step | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 11 | -2 | No | { } (empty) | Put 11 with index 0 |
| 2 | 1 | 15 | -6 | No | { 11=0 } | Put 15 with index 1 |
| 3 | 2 | 2 | 7 | No | { 11=0, 15=1 } | Put 2 with index 2 |
| 4 | 3 | 7 | 2 | Yes, at index 2 | { 11=0, 15=1, 2=2 } | Return [2, 3] |
Four steps end the whole thing. Let us look at each one closely.
Step 3 is the one worth studying. The partner existed, yet the lookup still failed.
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.
Same array, same target, three very different amounts of work.
| Approach | Steps taken | Array elements touched | Extra memory used |
|---|---|---|---|
| Brute force | 12 comparisons | All 7, many times over | None |
| Two-pass HashMap | 7 puts + 3 lookups | All 7 in pass one | Map of 7 entries |
| One-pass HashMap | 4 steps | Only the first 4 | Map 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. |
Tables are precise, but a sketch often lands faster. Here is the same one-pass trace drawn by hand.

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.
Some people read pictures faster than tables. Use whichever version clicks for you, or sketch your own on paper while you follow the code.
All three give a correct answer. They just pay different prices for it.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O(n squared) | O(1) | Simple to write, slow on big arrays |
| Two-pass HashMap | O(n) | O(n) | Fast, but walks the array twice |
| One-pass HashMap | O(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. |
A few small traps catch beginners here. Keep them in mind.
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.
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.
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.
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.
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].
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.
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