Stage 1: Foundations·Arrays & Hashing
Easy#16 min readHashingArraysLeetCode

Two Sum

HashMap lookup

HashMap lookup. The most fundamental pattern: trade space for time.

Published Apr 15, 2026

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.

Input
nums = [2, 7, 11, 15], target = 9
Output
[0, 1]
Explanation
nums[0] + nums[1] = 2 + 7 = 9
Input
nums = [3, 2, 4], target = 6
Output
[1, 2]
Explanation
Indices 1 and 2 point to 2 and 4.

Intuition

The obvious approach is a double loop: for every pair (i, j), check if nums[i] + nums[j] == target. That's O() — and it does far more work than necessary.

Here's the reframe. For each value x, the complement we need is fully determined: need = target - x. The question stops being "do any two numbers sum to target?" and becomes "have I already seen need?"

Answering that question in constant time is what a HashMap is for. As we walk the array, we keep a map of value index for every number seen so far. The nested search collapses into a single pass.

This is the most fundamental pattern in algorithmic thinking: trade space for time with a lookup table. You will reuse it for the rest of your career.

Approach

Brute Force

TimeO(n²)
SpaceO(1)

Try every pair. For each i, scan forward and check whether nums[i] + nums[j] == target. Obviously correct; quadratic. Useful as a baseline to motivate the optimization — and as the "can you think of anything better?" question in interviews.

impl Solution {
    pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
        for i in 0..nums.len() {
            for j in (i + 1)..nums.len() {
                if nums[i] + nums[j] == target {
                    return vec![i as i32, j as i32];
                }
            }
        }
        unreachable!("no valid pair found");
    }
}
Rust · runnable

HashMap Lookup

TimeO(n)
SpaceO(n)
Recommended

Walk nums once. For each value x, ask the map: have I seen target - x before? If yes, return that index paired with the current one. If no, record (x, i) and continue.

The order matters: check-then-insert, never insert-then-check. Otherwise x + x = target cases (like nums = [3, 3], target = 6) silently match a number with itself.

use std::collections::HashMap;

impl Solution {
    pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
        let mut seen: HashMap<i32, i32> = HashMap::with_capacity(nums.len());

        for (i, &x) in nums.iter().enumerate() {
            let need = target - x;
            if let Some(&j) = seen.get(&need) {
                return vec![j, i as i32];
            }
            seen.insert(x, i as i32);
        }

        unreachable!("no valid pair found");
    }
}
Rust · runnable

A few language notes:

  • RustHashMap::with_capacity(n) avoids a handful of rehashes. unreachable!() signals the invariant to LLVM for branch elimination. Iterating with .iter().enumerate() keeps us zero-copy.
  • TypeScriptMap over a plain object avoids prototype-key collisions (e.g. target - x === "constructor") and keeps insertion-order iteration. j !== undefined is required: a 0 index is falsy, so if (j) silently breaks the first test case.

Trace

Walking the HashMap approach through nums = [2, 7, 11, 15] with target = 9:

ixneedseen beforeactionseen after
027{}insert{2: 0}
172{2: 0}match → return [0, 1]

Two iterations. One allocation. The map never grows past one entry before we return.

Edge cases

  • Duplicate values where x + x == target — e.g. nums = [3, 3], target = 6. The check-before-insert ordering handles this: on i = 1, the value 3 from index 0 is already in the map.
  • Negative numbers — arithmetic is signed. No special handling needed.
  • No solution exists — the problem statement rules this out. If your production variant could see it, return Option<Vec<i32>> / number[] | null and remove the unreachable! / throw.
💡
Tip

When the input is sorted, the two-pointer approach solves this in O(1) extra space. See problem #167 (Two Sum II) — same problem, different constraints, completely different algorithm.

Takeaway

Whenever you see a brute force that asks "does there exist an x such that…?" inside a loop, ask: can I precompute that question into a HashMap or HashSet?

That single reflex unlocks dozens of problems on this roadmap — #217 Contains Duplicate, #242 Valid Anagram, #49 Group Anagrams, #128 Longest Consecutive Sequence all reduce to the same trade.

The pattern scales beyond leetcode. Every time you build a feature and find yourself scanning a list to answer the same question repeatedly, you're looking at the same transformation: index the data once, answer queries forever.

More in Arrays & Hashing