Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Return the indices (not values) of the two numbers that sum to target. Brute force is O(n²); the canonical answer is a single-pass hash map storing value → index, checking complement = target − current before inserting.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you reach for a hash map on 'find a pair' over an unsorted array — and whether you derive that from brute force instead of jumping straight to the answer.
Where candidates slip
Returning values [2, 7] instead of indices [0, 1], or inserting into the map before checking the complement (silently wrong on nums = [3, 3], target = 6).
How to stand out
State brute force first, then 'we can do better'. Name the pattern (complement lookup) and mention it shows up in Subarray Sum K, 3Sum, Two Sum II.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewFind two numbers that sum to targetIs the array sorted?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Nested Pair Scan
1Hint 1Explore hintHide hint
The problem guarantees exactly one answer — that means there is some pair (i, j) with i < j whose values add up to target. What's the simplest way to find such a pair?
2Hint 2Explore hintHide hint
Try every ordered pair (i, j) with j > i. For each pair, check whether nums[i] + nums[j] == target.
3Hint 3Explore hintHide hint
Two nested loops: outer over i from 0 to n-1, inner over j from i+1 to n-1. Return [i, j] the moment the sum matches.
A · Core idea
The most literal translation of the question into code: for every index i, walk every later index j > i and test whether nums[i] + nums[j] == target. Return the first pair that matches.
Why this is the natural first attempt. The problem asks for two indices that sum to target. The obvious question to ask in a nested loop is exactly the question the problem asked — one pointer fixes the first number, the other pointer tries every candidate second number. No clever insight needed, just mechanical enumeration.
Why `j = i + 1` and not `j = 0`. If j started at 0, we'd do two bad things: pair i with itself when i == j (producing false matches on even targets like nums[i] + nums[i] == 2*nums[i]), and re-check the same unordered pair twice — once as (i, j) and again as (j, i). Starting j at i + 1 fixes both problems for free: every unordered pair is visited exactly once, and self-pairing is impossible.
Why we don't ship this. The total work is n + (n−1) + (n−2) + … + 1 ≈ n²/2. For n = 10⁴ (the constraint) that's ~50 million comparisons — fast enough to pass Two Sum, but you're telling the interviewer you can't see the better answer. Mention this approach, state its cost, then reach for the hash map.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [3, 2, 4], target = 6Array state
i=0, j=1
3 + 2 = 5 — no match
[1, 2]C · Code with teaching comments
Reference implementation previewpublic 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};Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Starting j at i + 1 does double duty: it prevents pairing an index with itself, and it skips checking (a, b) and (b, a) separately. That's the first micro-optimization every candidate should state — it shows you think about loop invariants, not just syntax.
ComplexityO(n²) time, O(1) space — n(n−1)/2 ≈ n²/2 comparisons in the worst case; no extra data structures.
E · Edge cases and pitfalls3Starting j at 0 — pairs an element with itself.Explore edge casesHide edge cases
Pitfalls
- Starting j at 0 — pairs an element with itself.
- Returning nums[i], nums[j] instead of i, j — values vs indices.
- Skipping this approach because 'it's obvious' — interviewers grade for whether you mention it.
Approach 2 of 2 · Optimal
Hash Map — One Pass (Optimal)
1Hint 1Explore hintHide hint
If you knew, for each number x, whether target − x was somewhere else in the array, the problem would be trivial. What data structure answers 'have I seen this value before?' in O(1)?
2Hint 2Explore hintHide hint
Use a hash map keyed by value, valued by index. As you scan left-to-right, for each num compute complement = target − num and check the map before inserting.
3Hint 3Explore hintHide hint
If the complement is in the map, you've found your pair: return [map.get(complement), currentIndex]. Otherwise, put (num → currentIndex) into the map and continue. One pass, no second loop.
A · Core idea
Walk the array exactly once. At each position compute complement = target − nums[i]. If the complement has already been seen, return [seen[complement], i]. Otherwise record seen[nums[i]] = i and continue.
The mental shift that unlocks this. Brute force asks "does this pair sum to target?" for every pair — that's quadratic because there are n²/2 pairs. The hash map re-frames the question: for any single element x, the partner that would complete the pair is uniquely determined as target − x. So instead of enumerating pairs, we enumerate elements and ask one yes/no question per element: "have I already seen `target − x`?" A hash map answers that in O(1) average, so n elements × O(1) questions = O(n) time.
Why the insert happens after the check. The single most important line ordering in this algorithm is this: look up first, insert second. Consider nums = [3, 3], target = 6. On i = 0 we compute complement = 3. If we inserted first, the map would already contain 3 → 0, the lookup would succeed, and we'd return [0, 0] — matching the element against itself. By doing the lookup on an empty map at i = 0, then inserting 3 → 0, the second 3 at i = 1 finds the earlier one at index 0 and correctly returns [0, 1]. Same three lines of code, wrong order, silent bug.
Why `value → index` and not the reverse. The question we ask each step is "is this *value* one I've seen?" Hash maps give O(1) lookup only on the key, so the thing we look up has to be the key. Indices as keys would force us to scan the map every step — O(n) per step, O(n²) total — and we'd be back where we started.
Why we stop at the first match. The problem guarantees exactly one valid pair, so returning on the first hit is both correct and optimal. If the problem said "return all pairs", we'd keep going and append instead of returning.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [3, 2, 4], target = 6Hash-map state
i=0, num=3
3Continue scanning
complement = 3, map empty → insert 3 → 0
[1, 2]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The whole algorithm is a single trade: we pay O(n) extra memory to buy O(1) answers to the question "seen this value before?". Memory-for-time is the default trade in algorithm design — when you're stuck on a quadratic pair/triple/match problem, ask what you could remember to skip work, and a hash map is usually the answer. This exact shape recurs in Subarray Sum Equals K (remember prefix sums), Longest Consecutive Sequence (remember elements), and LRU Cache (remember recency).
ComplexityO(n) time, O(n) space — One pass × O(1) average map ops = O(n) time. Map holds up to n−1 entries → O(n) space.
E · Edge cases and pitfalls5nums = [3, 3], target = 6 — Empty map on i=0 → insert 3→0. On i=1 the complement 3 is found at 0 → return [0, 1]. Check-before-insert is what makes this work.Explore edge casesHide edge cases
Edge cases handled
nums = [3, 3], target = 6Empty map on i=0 → insert 3→0. On i=1 the complement 3 is found at 0 → return [0, 1]. Check-before-insert is what makes this work.nums = [0, 4, 3, 0], target = 0Insert 0→0, then on i=3 complement 0 is found at 0 → return [0, 3]. Tests 0 as both value and complement.
Pitfalls
- Inserting before checking — silently wrong on duplicates like [3, 3], target = 6.
- Storing index → value instead of value → index — makes the lookup O(n).
- In Python, `if seen.get(complement):` is falsy when the stored index is 0 — use `in seen`.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[] twoSum(int[] nums, int target) { Map<Integer, Integer> seen = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (seen.containsKey(complement)) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Returning values instead of indices
The prompt says 'indices' but the brain reads 'numbers'. Re-read the output spec before coding.
Explore the code comparisonHide the code comparison
Returning values instead of indices
The prompt says 'indices' but the brain reads 'numbers'. Re-read the output spec before coding.
Don't write this
if (nums[i] + nums[j] == target) {
return new int[]{nums[i], nums[j]};
}Write this instead
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}2Insert before check (duplicate trap)
On nums = [3, 3], target = 6, insert-first puts 3→0 in the map and the same iteration's complement lookup matches it — returning [0, 0].
Explore the code comparisonHide the code comparison
Insert before check (duplicate trap)
On nums = [3, 3], target = 6, insert-first puts 3→0 in the map and the same iteration's complement lookup matches it — returning [0, 0].
Don't write this
seen.put(nums[i], i);
int complement = target - nums[i];
if (seen.containsKey(complement)) {
return new int[]{seen.get(complement), i};
}Write this instead
int complement = target - nums[i];
if (seen.containsKey(complement)) {
return new int[]{seen.get(complement), i};
}
seen.put(nums[i], i);3Python truthiness on stored zero
If the stored index is 0, `if seen.get(complement):` evaluates to falsy and the check incorrectly fails. Always use `in seen`.
Explore the code comparisonHide the code comparison
Python truthiness on stored zero
If the stored index is 0, `if seen.get(complement):` evaluates to falsy and the check incorrectly fails. Always use `in seen`.
Don't write this
if seen.get(complement):
return [seen[complement], i]Write this instead
if complement in seen:
return [seen[complement], i]Revise
Carry the pattern into the interview
Pattern to remember
Complement lookup via hash map
Code pattern to memorize
for each (i, x) in nums:
if (target - x) in seen: return [seen[target - x], i]
seen[x] = i # insert AFTER the checkRules to remember
- 1Return indices, not values — the most common misread of this prompt.
- 2Check
target − xin the map before insertingx. Flipping those two lines silently breaks[3, 3], target = 6. - 3Store
value → index, neverindex → value— you needO(1)membership on the value. - 4In Python use
complement in seen, notif seen.get(complement):— a stored index of0is falsy. - 5Brute force first, then optimal. Interviewers grade for the progression, not just the final answer.
Use this pattern when
- Unsorted array + find a pair / match that sums or XORs to a target
- Need the original **indices**, not the values
- Seeing one element, asking 'have I already seen the partner?'
- Array access is random; streaming / one-pass is fine
Choose another pattern when
- Array is already sorted → two pointers is O(1) space (Two Sum II)
- Need only the values, not indices → sort then two-pointer
- Need all pairs, not just one → 3Sum-style nested two-pointer
- Space is `O(1)`-hard-constrained → brute force or sort, not a hash map
Interview takeaway
Hash-map complement lookup is the interview-grade trick for turning pair-finding into membership testing. Once you see it, you'll spot it in Subarray Sum Equals K, 3Sum, Group Anagrams, and LRU Cache — it's one pattern, many problems.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical Hash Map for Complement Lookup. Recognise it whenever you need to find a pair (or any 'match') in O(n) on an unsorted array. Same shape as Subarray Sum Equals K, Group Anagrams, and Top K Frequent Elements.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Two Sum II — Sorted ArrayArray is sorted — two pointers, O(n) time, O(1) space.
- 3SumComing soonSort, fix one element with an outer loop, two-pointer the other two. O(n²).
- 4SumComing soonExtend 3Sum — two outer loops, two-pointer the inner pair. O(n³).
- Subarray Sum Equals KComing soonSame complement-lookup pattern, but on prefix sums.