Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Two Sum
The classic warm-up: find the two numbers that add up to a target. Brute force is O(n²) — a hash map gets you to one pass, O(n). Solve it in Python or TypeScript, in your browser.
The problem
Given an array of integers nums and an integer target, return the indices of the two numbers that add up to target. Each input has exactly one solution, and you may not use the same element twice. Return the indices in increasing order.
Your turn — write it
Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.
Implement two_sum(nums, target): return the two indices (increasing order) whose values sum to target. One pass with a hash map of value → index gets you O(n).
Examples and constraints 4 examples
nums = [2, 7, 11, 15], target = 9[0, 1]nums = [3, 2, 4], target = 6[1, 2]nums = [3, 3], target = 6[0, 1]nums = [-3, 4, 3, 90], target = 0[0, 2]- 2 ≤ nums.length ≤ 10⁴
- Exactly one valid answer exists.
- Aim for O(n) time — one pass with a hash map beats the O(n²) double loop.
Hints 4 ideas
- Brute force checks every pair — O(n²). The trick is to remember what you have already seen.
- Walk the array once, keeping a map of
value → index. - At each number
x, the partner you need istarget - x. If it is already in the map, you are done. - Store
x → its indexonly after checking, so you never pair an element with itself.
Approach, complexity & discussion — open after you solve
Start from brute force
Try every pair. Two nested loops pick i, then every j > i, and return [i, j] the first time nums[i] + nums[j] == target. Starting j at i + 1 means no element is paired with itself and the indices come out in increasing order, so the answer is always right. The cost is the problem: there are n(n − 1)/2 pairs, so time is O(n²) while extra space is O(1). At the upper constraint of 10⁴ numbers that is 49,995,000 checks when the answer is the last pair. Each outer step rescans the rest of the array to ask one question: is the partner of nums[i] out there?
The approach
For any number x the partner is fixed: it has to be target − x. That turns the pair search into a membership test, “have I already passed target − x?”, and a hash map answers it in O(1) on average. The reference solution walks the array once with enumerate. For each index i and value x it computes need = target - x and checks need in seen. On a hit it returns [seen[need], i]; the stored index is always smaller than i, so the pair is already in increasing order. On a miss it records seen[x] = i and moves on.
The invariant: when the loop reaches index i, seen holds the values of nums[0..i-1] and nothing else. That is why the check comes before the insert. x can only match an earlier element, never itself, while the second 3 in [3, 3] still finds the first. The answer is found at its right-hand element, because its left-hand element is already in the map. The TypeScript twoSum does the same with a Map.
Walk through an example
The first example ends after two steps, so here is nums = [3, 8, 1, 3], target = 6, where the first 3 waits until the end for its partner.
| step | i | x | need | seen before the check | result |
|---|---|---|---|---|---|
| 1 | 0 | 3 | 3 | {} | miss (3 cannot pair with itself); store 3 → 0 |
| 2 | 1 | 8 | -2 | {3: 0} | miss; store 8 → 1 |
| 3 | 2 | 1 | 5 | {3: 0, 8: 1} | miss; store 1 → 2 |
| 4 | 3 | 3 | 3 | {3: 0, 8: 1, 1: 2} | hit: return [0, 3] |
Complexity
Hash map: O(n) time, because each element costs one average-O(1) lookup and at most one insert, and O(n) space, because seen can hold n − 1 entries when the pair is the last two elements. Brute force: O(n²) time for n(n − 1)/2 pair checks and O(1) space. The map buys linear time with linear memory.
Edge cases to test
nums = [3, 3],target = 6→[0, 1]. Equal values are different elements; inserting before checking returns[0, 0].nums = [3, 2, 4],target = 6→[1, 2], not[0, 0]: 3 is half the target but appears once, so it must not pair with itself.nums = [-3, 4, 3, 90],target = 0→[0, 2]. Negatives break any shortcut that skips values larger than the target.nums = [2, 7, 11, 15],target = 9→[0, 1]. The partner sits at index 0, so a check written asif seen.get(need):treats it as missing and returns[]; test membership withneed in seen.nums = [8, 1, 2, 4],target = 6→[2, 3]. The pair is the last two elements, so the map fills to n − 1 entries first: the worst case for time and memory.
Common mistakes
- Pairing an element with itself — check for the complement before inserting the current value.
- Assuming the array is sorted (that is the two-pointer solution) — the hash-map approach needs no ordering.
- Returning the values instead of the indices the problem asks for.
- Rebuilding the “seen” map each iteration instead of carrying it through the single pass.
Follow-ups an interviewer asks next
- The array is sorted. Can you drop the hash map?
- Yes. Put
loat 0 andhiat the last index. Ifnums[lo] + nums[hi]is below the target, moveloright; if above, movehileft; stop when it matches. Each move discards an element that cannot be in any remaining pair, because sorted order means no partner left can fix its sum. That is O(n) time and O(1) space. - Return every pair of values that sums to the target, without duplicates.
- Keep scanning instead of returning at the first hit. Store seen values in a set, and when
target − xis in it, add the normalized pair(min(x, target − x), max(x, target − x))to a result set so 1 + 5 and 5 + 1 count once. That stays O(n) time and space. Listing every index pair instead can be O(n²) output: an array of all 3s with target 6. - The numbers arrive as a stream, or there are too many for one machine.
- The one-pass loop is already an online algorithm: check each arriving value, then insert it, using memory for the distinct values seen so far. To split the work, partition on the key
min(x, target − x). Both members of a pair produce the same key, because the partner oftarget − xisx, so every pair lands in one partition and each worker runs the same map. - Now find three numbers that sum to zero.
- Sort once, then fix each element
aand run the two-pointer scan for-aon the part to its right. That is O(n²) time and O(1) extra space beyond the sort, and skipping equal neighbors after a match removes duplicate triplets. A hash-map version also reaches O(n²) but makes the deduplication harder to get right.
Where this shows up
“Remember what you’ve seen in a hash map, then answer in O(1)” is one of the most reused patterns in all of engineering — deduplication, hash joins, caches, detecting repeats in a stream, and reconciliation all run on it. Two-sum is the smallest possible instance of trading memory for a linear-time lookup.
Reference solution
Reference solution — Python
def two_sum(nums, target):
seen = {} # value -> index we've already passed
for i, x in enumerate(nums):
need = target - x # the partner that completes the pair
if need in seen:
return [seen[need], i] # found: earlier index first
seen[x] = i # remember x for a future partner
return [] # no pair (the prompt promises one exists)
print(two_sum([2, 7, 11, 15], 9)) # -> [0, 1]
print(two_sum([3, 2, 4], 6)) # -> [1, 2]Reference solution — TypeScript
function twoSum(nums: number[], target: number): number[] {
const seen = new Map<number, number>(); // value -> index already passed
for (let i = 0; i < nums.length; i++) {
const need = target - nums[i]; // partner that completes the pair
if (seen.has(need)) {
return [seen.get(need)!, i]; // found: earlier index first
}
seen.set(nums[i], i); // remember for a future partner
}
return []; // no pair (prompt promises one)
}
console.log(twoSum([2, 7, 11, 15], 9)); // -> [0, 1]
console.log(twoSum([3, 2, 4], 6)); // -> [1, 2]Questions about Two Sum
Why does the hash map solution check for the complement before inserting the current number?
Because the lookup must only see earlier elements. If you insert x first and then look up target minus x, a number that is exactly half the target finds itself: for [3, 2, 4] with target 6 that version returns [0, 0], using the single 3 twice. Checking first means the map holds only indices before the current one, so any match is a different element. Duplicates still work, because in [3, 3] the second 3 finds the first and the answer is [0, 1]. The order also guarantees the stored index is the smaller one, so the pair comes out sorted.
Can Two Sum be solved in O(1) extra space?
Yes, but not in linear time on unsorted input. The nested loop uses O(1) extra space and O(n²) time. Sorting in place with an algorithm such as heapsort and then scanning with two pointers uses O(1) extra space and O(n log n) time, but sorting destroys the original positions, and this problem asks for indices. Reporting them means keeping a copy of the array or a list of original indices, which is O(n) space again. Only when the input is already sorted do you get O(n) time and O(1) space together.
Is sorting plus two pointers better than a hash map for Two Sum?
Not for this version. Sorting costs O(n log n) time, which is slower than the one-pass map, and because the answer is a pair of original indices you have to sort (value, index) pairs, which takes O(n) memory anyway. Two pointers win when the input arrives already sorted, the variant often called Two Sum II, where they give O(n) time and O(1) space with no map at all. They are also the usual building block for Three Sum, where one sort is cheap next to the O(n²) scan.
Which pair does the one-pass solution return if there are several, or none?
This problem promises exactly one answer, but the code is still well defined without that promise. It returns the first pair completed, the one whose right-hand index is smallest: for [1, 5, 2, 4] with target 6 it returns [0, 1] and never looks at 2 and 4. When the left value repeats, the map keeps the latest index, because seen[x] = i overwrites, so [2, 2, 4] with target 6 gives [1, 2]. If the loop finishes without a hit, the reference solution returns an empty list.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- Valid ParenthesesThe canonical stack problem: decide whether every bracket is closed by the right type, in the right order. A stack turns nested matching into a single pass. Solve it in Python or TypeScript with hidden tests.Read →
- Fizz BuzzThe famous screening question. Print 1…n, but multiples of 3 become "Fizz", of 5 become "Buzz", and of both become "FizzBuzz". Easy — the catch is testing divisibility in the right order. Solve it in Python or TypeScript.Read →
- SoftmaxThe function at the end of every classifier and language model: turn raw scores (logits) into a probability distribution. Implement the numerically stable version so big logits do not overflow. Solve it in Python or TypeScript.Read →
- Cosine SimilarityThe measure behind every embedding search and RAG system: how aligned are two vectors, ignoring their length? Dot product over the product of magnitudes — 1 identical, 0 orthogonal, -1 opposite. Solve it in Python or TypeScript.Read →
- Top-K RetrievalThe core of the "R" in RAG: given a query embedding and a set of document embeddings, return the indices of the k most similar docs by cosine similarity, with a stable tie-break. Solve it in Python or TypeScript.Read →
- Token-Level F1The metric behind QA evaluation (SQuAD and friends): how well does a predicted answer overlap a reference as a bag of words? Compute token precision and recall, then their harmonic-mean F1. Solve it in Python or TypeScript.Read →