AlgorithmsBeginner

Two Sum Algorithm in Python

Learn how to solve the classic Two Sum algorithm in Python! An incredibly fun tutorial on using Hash Maps to upgrade your code from slow O(n^2) to blazing fast O(n).

Try it yourself

Run this code directly in your browser. Click "Open in full editor" to experiment further.

Loading...

Click Run to see output

Or press Ctrl + Enter

How it works

Welcome to LeetCode #1, the most famous coding puzzle on Earth! 🌍 The leetcode two sum problem is the gateway drug to algorithmic thinking and the moment the hash map python lightbulb finally clicks. πŸ’‘

It looks simple β€” find two numbers that add up to a target β€” but tucked inside this array problem is a gorgeous lesson about trading memory for speed. By the end you will know three solutions and why this interview question has stayed #1 for over a decade.

The Problem Statement

Given an array 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.

Quick 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] (duplicates allowed!)
  • We return indices, not values. That detail trips up more people than you would believe.

    Approach 1: Brute Force

    The most natural first instinct: just try every pair. 🐒

    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]

    O(n^2) time, O(1) space. For length 10,000 that is ~50M checks. Still acceptable for tiny n, throwaway scripts, or as a baseline before optimising. Interviewers want to hear "naive is O(n^2), can we do better?" βœ…

    Approach 2: Two-Pass Hash Map

    First leap into hash-map land: build a dictionary of value β†’ index, then loop again looking for the complement.

    def two_sum_two_pass(nums, target):
        num_map = {num: i for i, num in enumerate(nums)}
        for i, num in enumerate(nums):
            complement = target - num
            if complement in num_map and num_map[complement] != i:
                return [i, num_map[complement]]
        return []

    O(n) time, O(n) space. The != i check is critical β€” without it, [3, 5] with target = 6 would return [0, 0]. We cannot reuse the same element. ⚠️

    Approach 3: One-Pass Hash Map

    The elegant trick: *check the map while you are building it*. The first time you see a number, record it. When its partner shows up later, the earlier number is already waiting.

    def two_sum(nums, target):
        seen = {}
        for i, num in enumerate(nums):
            complement = target - num
            if complement in seen:
                return [seen[complement], i]
            seen[num] = i
        return []

    Worked example: `nums = [2, 7, 11, 15]`, `target = 9`

    Stepinumcomplementcomplement in seen?Actionseen after
    1027Nostore 2 β†’ 0{2: 0}
    2172Yes!return [0, 1]β€”

    Two iterations, done. ⚑ We never even look at 11 or 15. The single-pass version is also safer β€” we always look backwards into a map that does not yet contain the current element.

    Complexity Analysis

    ApproachTimeSpacePassesNotes
    Brute ForceO(n^2)O(1)~n/2 avgSimple, fine for tiny inputs
    Two-Pass Hash MapO(n)O(n)2Easier to explain, watch the self-pair bug
    One-Pass Hash MapO(n)O(n)1The canonical answer in interviews

    A textbook space–time tradeoff: O(n) memory slashes time from quadratic to linear β€” a model that carries through DP, memoisation, and graph algorithms.

    Common Pitfalls

    1. Returning values instead of indices. The problem asks for [i, j], not [nums[i], nums[j]]. πŸ‘€

    2. Returning the same index twice. If target = 2 * nums[i] and you do not guard against j == i, you may pair an element with itself. The one-pass version sidesteps this naturally.

    3. Mishandling duplicates. nums = [3, 3], target = 6 is legit β€” your logic must allow two equal values at different indices.

    4. Mutating the input. Sorting nums to use two pointers destroys original indices. If you must sort, sort (value, index) tuples instead.

    5. Assuming a solution always exists. LeetCode #1 guarantees one; real life does not. Decide your no-pair return value ([], None, raise) and document it.

    6. Confusing this with the sorted variant. Sorted input (LC 167) calls for two pointers in O(1) space, not a hash map. Different problem, different tool.

    Variants You Will See

  • Two Sum II β€” Sorted (LC 167): use two pointers β€” one at each end, move inward based on the sum. O(n) time, O(1) space.
  • 3Sum (LC 15): all unique triplets summing to zero. Sort, then for each element run two-pointer on the rest. O(n^2).
  • 4Sum (LC 18): same idea, one more nested loop. O(n^3).
  • k-Sum (generalised): recurse down to two-pointer as the base case.
  • Two Sum with duplicates / count of pairs: track frequencies with Counter.
  • Closest pair to target: sort + two pointers, minimise abs difference.
  • Why Interviewers Love This Problem

    Two Sum is a near-perfect 15-minute screen. One small problem grades:

  • Array iteration fluency β€” clean enumerate, no off-by-ones.
  • Hash map intuition β€” do you reach for a dict when you smell repeated lookups?
  • Complexity reasoning β€” can you articulate the O(n^2) vs O(n) tradeoff?
  • Edge-case awareness β€” duplicates, negatives, empty arrays, no solution.
  • Communication β€” do you propose the optimisation aloud, or silently type?
  • A vibes check disguised as an algorithm. πŸ•΅οΈ

    Real-World Uses

    The "two values summing to a target" pattern is everywhere once you spot it:

  • Financial pair-trades: two assets whose combined exposure hits a dollar target.
  • Recommendation systems: pair items whose combined relevance scores match a budget.
  • Reconciliation: matching debits to credits, invoices to payments.
  • Set-membership lookups: "have I seen this before?" uses the same hash reflex.
  • The general pattern: trade time for space with hashing β€” caches, memoisation, indexing, bloom filters.
  • Frequently Asked Questions

    Why is the hash map approach $O(n)$? Each iteration is an O(1) average-case dict lookup plus insert. Done n times, that is O(n) total. Python's dict is engineered to avoid the pathological collision case.

    Multiple valid pairs? LeetCode #1 promises one. In real inputs, decide: first found, all pairs, or lex-smallest.

    What if no pair sums to target? Return a sentinel β€” [], None, or raise ValueError. Our code returns []. Pick a convention and stick to it.

    Should I sort and use two pointers? Only if it is already sorted or you do not need original indices. Sorting is O(n log n), worse than the hash map's O(n).

    Does it work with negative numbers? Yes. complement = target - num is happy with negatives, zero, and floats. nums = [-3, 4, 3, 90], target = 0 returns [0, 2].

    Two Sum vs 3Sum β€” how does it generalise? k-Sum is "fix one element, run (k-1)-Sum on the rest", with Two Sum as the base case. Master Two Sum and half of 3Sum is done. 🎯

    Related examples