Run it
All three approaches with their operation counts, then the self-pairing bug demonstrated by moving one line, and finally a size where the quadratic version stops being viable.
The brute force, and why to mention it
O(n²) time, O(1) space. State it, give the complexity, and move on to the improvement — that sequence demonstrates you can see the baseline and improve on it, which is part of what is being assessed.
Do not spend time writing it out unless asked.
The hash-map solution
One pass, O(n) time and O(n) space.
The insight worth stating out loud: "for each number, I need to know whether its complement has already appeared" — that is a lookup, and a dictionary makes lookups constant time. The nested loop was re-scanning to answer a question a dictionary answers immediately.
Two details that matter:
Check before inserting. If you store x first and then look for target - x, an input like nums = [3, 5], target = 6 matches 3 against itself and returns [0, 0].
One pass is enough. A two-pass version — build the whole dictionary, then scan — also works and needs the same self-pairing guard. The one-pass version is cleaner and is what interviewers expect.
Turning a search into a lookup
The nested-loop version asks "does any later element pair with this one?", which is n²/2 comparisons. The insight is to invert it: you know exactly which number you need, so the question becomes "have I already seen target - value?" — and that is a dictionary lookup, not a search.
This is the same move as grouping anagrams by a key. Whenever a problem asks you to find a pair with a known relationship, look for the version where one member is computed rather than searched for.
The two orderings that matter
Check before you store. Storing first lets an element pair with itself when target is exactly twice it — [3] with target 6 returns (0, 0), which is wrong.
Store value → index, not the reverse. The values are what you look up; the indices are what you return. Duplicates overwrite, which is fine because any one valid pair is usually acceptable — ask, if the question does not say.
When to use two pointers instead
If the input is already sorted, converging pointers solve it in O(n) time and O(1) space: too small means move the left pointer right, too big means move the right pointer left. That is strictly better on memory.
If it is not sorted, sorting to enable that is O(n log n) — worse than the dictionary, and it destroys the original indices, which the question usually asks for. Say why you are choosing the dictionary; that reasoning is most of the mark.
Walking through an example
nums = [2, 7, 11, 15], target = 9:
| i | x | Need | In seen? | Action |
|---|
| 0 | 2 | 7 | No | seen = {2: 0} |
| 1 | 7 | 2 | Yes, at index 0 | Return [0, 1] |
Two iterations. Tracing a small example aloud is worth doing in an interview — it catches off-by-one and ordering errors before the interviewer has to point them out.
Edge cases to raise
| Case | Behaviour |
|---|
| No solution exists | Return [] or None — ask which is expected |
Duplicate values, e.g. [3, 3] target 6 | Works: the second 3 finds the first |
Same element twice, [3] target 6 | Must not match — the check-before-insert order handles it |
| Negative numbers | Works unchanged; the arithmetic does not care |
| Several valid pairs | Returns the first found; ask whether all are wanted |
| Empty or single-element array | Return the empty result |
Raising the "same element twice" case unprompted is a good signal, because it is the one the naive ordering gets wrong.
The sorted variant: two pointers
If the array is sorted, O(1) space is achievable:
The correctness argument: if the sum is too small, the only way to increase it is to raise the left value; if too large, lower the right one. No pair is ever skipped.
| | Hash map | Two pointers |
|---|
| Requires sorted input | No | Yes |
| Time | O(n) | O(n) |
| Space | O(n) | O(1) |
| Returns original indices | Yes | Only if sorted in place |
That last row is a real trap: if you sort an unsorted array to use two pointers, the indices you return refer to the sorted array, not the original. Sorting (value, index) pairs preserves them, and at that point the hash map is simpler.
The follow-ups interviewers ask
"What if you need all pairs?" Do not return early; collect matches. Watch for duplicates — sort and skip repeated values, or track which indices are used.
"What about three numbers summing to the target?" Three-sum: sort, fix one element with an outer loop, and two-pointer the rest. O(n²). This is the most common follow-up, and being ready for it is worth more than optimising two-sum further.
"What if the array is enormous and does not fit in memory?" Sort externally and use two pointers with streaming reads, or partition by hash across machines.
"Can you do it in O(1) space without sorting?" Not in O(n) time. That trade — O(n) space for O(n) time, or O(1) space for O(n log n) including the sort — is the honest answer, and saying so is better than searching for something that does not exist.
"What if the same number can be used twice?" Then insert before checking, and [3] with target 6 returns [0, 0].
What the question is testing
Two-sum is almost never about two-sum. It is checking whether you:
Recognise a lookup. Converting a nested scan into a hash-map lookup is the single most common optimisation in practical code, and this is the smallest problem that demonstrates it.
State complexity accurately, including space, without prompting.
Handle the self-pairing case, which is the one real subtlety.
Ask about the input. Sorted? Duplicates? Guaranteed solution? All valid pairs or one?
Communicate while coding rather than silently producing an answer.
The pattern generalises far beyond the interview: any time a loop contains a search, a dictionary built beforehand usually removes it. That is the transferable lesson.
Recap in one screen
- Ask whether the array is sorted — it changes the answer.
- Unsorted: one pass with a dictionary from value to index, O(n) time and O(n) space.
- Check for the complement before inserting the current value, or an element pairs with itself.
- Sorted: two pointers from both ends, O(n) time and O(1) space, moving the pointer that corrects the sum.
- The real lesson is turning a repeated scan into a constant-time lookup; three-sum is the standard follow-up.