Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

CODING CHALLENGE · N°01

Two Sum

Easy ArraysHash Map

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.

SOLVE IT YOURSELF

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.

YOUR TASK

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).

CPython · WebAssembly
Examples and constraints 4 examples
EXAMPLE 1
Input nums = [2, 7, 11, 15], target = 9
Output [0, 1]
nums[0] + nums[1] = 2 + 7 = 9
EXAMPLE 2
Input nums = [3, 2, 4], target = 6
Output [1, 2]
2 + 4 = 6
EXAMPLE 3
Input nums = [3, 3], target = 6
Output [0, 1]
Edge case: the two 3s are different elements, so using both is allowed.
EXAMPLE 4
Input nums = [-3, 4, 3, 90], target = 0
Output [0, 2]
Negative numbers and a zero target need no special handling: -3 + 3 = 0.
CONSTRAINTS
  • 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
HINTS — 4 IDEAS
  1. Brute force checks every pair — O(n²). The trick is to remember what you have already seen.
  2. Walk the array once, keeping a map of value → index.
  3. At each number x, the partner you need is target - x. If it is already in the map, you are done.
  4. Store x → its index only 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.

stepixneedseen before the checkresult
1033{}miss (3 cannot pair with itself); store 3 → 0
218-2{3: 0}miss; store 8 → 1
3215{3: 0, 8: 1}miss; store 1 → 2
4333{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 as if seen.get(need): treats it as missing and returns []; test membership with need 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 lo at 0 and hi at the last index. If nums[lo] + nums[hi] is below the target, move lo right; if above, move hi left; 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 − x is 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 of target − x is x, 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 a and run the two-pointer scan for -a on 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.

▶  Watch it explained
Finished this one? 0 / 76 Challenges done
NextValid Parentheses Next on the “Interview classics” ladder · rung 3 of 10

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.