Two Sum in Java DSA: From Brute Force to the One-Pass HashMap
-
Last Updated: July 28, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Learn Two Sum in Java DSA step by step, from brute force to the one-pass HashMap. Full dry runs, clean code, and complexity comparison for beginners.
Two Sum in Java is the problem almost everyone starts with. It shows up on day one of most interview prep lists, and for good reason. It teaches a trick you will reuse in dozens of harder problems: trading a bit of memory to save a lot of time.
The task is short. You get an array of numbers and a target. You must find two numbers that add up to that target, and return their positions.
There is one small promise that makes life easier. Each input has exactly one answer, and you cannot use the same element twice. So you never have to worry about ties or missing pairs.
We solve it in three steps, from slow to fast. Brute force checks every pair with two loops. A sort-and-two-pointer version trims the search on a sorted copy. The one-pass HashMap is the version interviewers actually want, and it runs in a single sweep.
Every approach gets a full, step-by-step dry run on the same five numbers. Nothing is skipped, so you can see exactly what each line does and what changes on every step.
Let us pin down the rules before we touch any code.
For our array the answer is [0, 3], because nums[0] is 3 and nums[3] is 7, and 3 + 7 makes 10. Notice we return indices, not the values. That detail matters later, and it decides which approach truly wins.
For any number x, the partner we need is target minus x. We call that the complement. If x is 3 and the target is 10, then we are hunting for a 7 somewhere else in the array.
This one idea powers the fast solution. Instead of testing pairs blindly, we look up the exact partner we want.
A HashMap lets us ask “have I seen this value already?” in roughly constant time. We store each number as we pass it, with its index as the value. Later, a single lookup tells us if the complement went by earlier.
If we sort the numbers first, we can walk two pointers inward from both ends. One pointer lower starts on the left, the other pointer higher starts on the right. The sort tells each pointer which way to move.
| 💡 Interview Insight A classic opener is “can you do better than checking every pair?” Mention the complement trick early. Saying “for each number I look up target minus that number” signals you already see the O(n) path. |
Try every possible pair of numbers. Keep the pair that hits the target. It is slow, but it proves you understand the goal.
for i from 0 to n-1: // first number
for j from i+1 to n-1: // second number
if nums[i] + nums[j] == target:
return [i, j]
return [] // no pair (won't happen here)import java.util.*;
public class TwoSumBrute {
public static int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[] { i, j };
}
}
}
return new int[] {}; // no pair found
}
public static void main(String[] args) {
int[] nums = { 3, 2, 6, 7, 11 };
System.out.println(Arrays.toString(twoSum(nums, 10))); // [0, 3]
}
}Array: [3, 2, 6, 7, 11], target 10. The two loops try pairs in order until one works. We trace every attempt, so nothing is hidden.
| Step | i (val) | j (val) | nums[i] + nums[j] | = 10? | Action |
|---|---|---|---|---|---|
| 1 | 0 (3) | 1 (2) | 3 + 2 = 5 | no | keep going, move j right |
| 2 | 0 (3) | 2 (6) | 3 + 6 = 9 | no | keep going, move j right |
| 3 | 0 (3) | 3 (7) | 3 + 7 = 10 | YES | return [0, 3] |
Let us walk the trace one step at a time and watch the two loops move.
The outer loop fixes i on the first number, which is 3. Now j sweeps every number to its right, looking for a partner that makes 10. So j is really hunting for a 7.
Notice the method never needed the outer loop to advance past index 0. The very first number found its partner. On a bigger or unlucky array, though, i would keep marching and j would restart each time, which is exactly why this approach gets slow.
Three checks for five numbers feels fine. For a few thousand numbers the pair count explodes, which is why we sharpen it next.
Sort a copy of the array, then send two pointers inward from both ends. The sort gives each pointer a reliable direction, so we skip huge chunks of pairs at once.
One honest catch: sorting scrambles the original positions. So this version naturally finds the two values, not their original indices. We will see that clearly in the dry run.
sort a copy of nums
lower = 0
higher = n - 1
while lower < higher:
sum = arr[lower] + arr[higher]
if sum == target: return the two values
else if sum < target: lower = lower + 1
else: higher = higher - 1import java.util.*;
public class TwoSumTwoPointer {
public static int[] twoSum(int[] nums, int target) {
int[] arr = nums.clone();
Arrays.sort(arr);
int lower = 0, higher = arr.length - 1;
while (lower < higher) {
int sum = arr[lower] + arr[higher];
if (sum == target) {
return new int[] { arr[lower], arr[higher] };
} else if (sum < target) {
lower++;
} else {
higher--;
}
}
return new int[] {};
}
public static void main(String[] args) {
int[] nums = { 3, 2, 6, 7, 11 };
System.out.println(Arrays.toString(twoSum(nums, 10))); // [3, 7]
}
}Sorted copy: [2, 3, 6, 7, 11], target 10. We trace every while step, including the moves that find nothing, so the pointer motion is fully visible.
| Step | lower (val) | higher (val) | sum | vs 10 | Action |
|---|---|---|---|---|---|
| 1 | 0 (2) | 4 (11) | 2 + 11 = 13 | more | sum too big, move higher left |
| 2 | 0 (2) | 3 (7) | 2 + 7 = 9 | less | sum too small, move lower right |
| 3 | 1 (3) | 3 (7) | 3 + 7 = 10 | equal | MATCH, return values (3, 7) |
Legend: lower is the left pointer moving right, higher is the right pointer moving left, and sum adds the two pointed values.
Let us follow the two pointers step by step and see why each one moves the way it does.
Before we start, lower sits at index 0 on the value 2, and higher sits at index 4 on the value 11. We want the two pointed values to add up to 10.
Three steps settled it, and the pointers never crossed back over ground they already covered. That is the whole charm of two pointers: every move rules out a batch of pairs, not just one.
But look closely at what we returned: the values 3 and 7, not their spots in the original array. Two Sum wants indices, and sorting threw those away. That gap is exactly why the next approach wins.
| 💡 Interview Insight If you pitch the two-pointer method, get ahead of the follow-up. Say plainly that sorting loses the original indices, so you would either store index pairs before sorting or, cleaner still, reach for a HashMap. Naming the trade-off shows real depth. |
Faster than brute force on large inputs. Still, the sort adds cost and the index problem lingers, so we go leaner one more time.
This is the answer interviewers hope to see. We walk the array once. For each number we ask the map whether its complement already went by. If yes, we are done; if no, we drop the current number in and keep going.
seen = empty map from value to index
for i from 0 to n-1:
need = target - nums[i]
if need is in seen:
return [ seen[need], i ]
put nums[i] -> i into seen
return []import java.util.*;
public class TwoSumHashMap {
public static 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[] {};
}
public static void main(String[] args) {
int[] nums = { 3, 2, 6, 7, 11 };
System.out.println(Arrays.toString(twoSum(nums, 10))); // [0, 3]
}
}Array: [3, 2, 6, 7, 11], target 10. We trace every index, showing the complement, the map before the check, and what changes after. The map starts empty.
| i (val) | need = 10 – val | map before | need in map? | Action | map after |
|---|---|---|---|---|---|
| 0 (3) | 7 | { } | no | store 3 | { 3:0 } |
| 1 (2) | 8 | { 3:0 } | no | store 2 | { 3:0, 2:1 } |
| 2 (6) | 4 | { 3:0, 2:1 } | no | store 6 | { 3:0, 2:1, 6:2 } |
| 3 (7) | 3 | { 3:0, 2:1, 6:2 } | YES (3 at idx 0) | return [0, 3] | — |
Let us go index by index and watch the map decide each step.
One timing rule ties it together. We check for the complement first, then store the current number. That order is what stops a number from wrongly matching itself, and it is why the 3 from index 0 was sitting ready when its partner 7 arrived.
Also notice the payoff over brute force. We touched each number once and never looped back. The map did the searching for us, so five numbers meant four quick checks instead of a growing pile of pairs.
| 💡 Interview Insight Interviewers often ask why you store the number after the check, not before. The answer: storing first would let a single element pair with itself when the target is exactly double its value. Checking first keeps every pair honest. |
Tables are precise, but a sketch often lands faster. Here is the same one-pass HashMap trace drawn by hand.

All three can find a valid pair. They just pay different prices, and one of them also solves the index problem cleanly.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O(n squared) | O(1) | Simple, but two loops crawl on big arrays |
| Sort + two pointers | O(n log n) | O(n) | Faster, yet sorting loses original indices |
| One-pass HashMap | O(n) | O(n) | Fastest, returns indices, the expected answer |
The HashMap wins on two fronts. It runs in a single pass, and it returns the exact indices the problem asks for. The two-pointer method is clever, but for Two Sum specifically it fights the index requirement.
In an interview, start with brute force, point out the wasted second loop, then jump to the HashMap and explain the complement trick. That climb is the story interviewers want to hear.
| 💡 Interview Insight If asked about worst-case HashMap cost, be honest: lookups are average O(1), not guaranteed. With adversarial hash collisions they can degrade, but for interview purposes the one-pass map is treated as O(n) overall. |
A few small traps catch beginners on Two Sum. Keep them in mind.
Run a tiny case like [3, 3] with target 6 through your code. It quickly reveals a self-pairing bug if you stored before checking.
A: The one-pass HashMap runs in O(n) time. For each number you look up its complement (target minus the number) in the map. If it is already there, you have your pair; if not, you store the current number and move on.
A: Storing first would let a single element pair with itself when the target is exactly double its value. Checking the complement first, then storing, keeps every pair made of two different positions.
A: You can, but only after sorting, and sorting throws away the original positions. Since Two Sum asks for indices, the two-pointer method needs extra bookkeeping. The HashMap returns the correct indices directly, so it is the cleaner choice here.
A: Brute force is O(n squared) time and O(1) space. Sort plus two pointers is O(n log n) time and O(n) space. The one-pass HashMap is O(n) time and O(n) space, and it is the expected interview answer.
Two Sum in Java looks tiny, yet it hides a habit you will use everywhere. Brute force grinds through pairs. The HashMap flips the problem around and asks a smarter question: have I already seen the partner I need?
Our five-number trace made the payoff clear. The brute force checked pairs until one worked. The HashMap swept once, remembered as it went, and found the answer the instant the complement showed up.
So take the pattern, not just the answer. When you catch yourself testing every pair, ask whether a map of what you have seen could answer the question in one look. That instinct will carry you through many array problems further down the list.
javahandson.com | DSA Series | Arrays & Strings