Skip to content

How a lesson works

Every lesson has the same parts, in the same order. This page shows each part once, using a classic problem: Two Sum.

You have a gift card worth exactly 50. The menu prices are 12, 35, 7, 15 and 44. Which two items use up the whole card?

Lessons start with an everyday problem like this, before any technical words.

Now the exact version:

  • Input: a list nums and a number target.
  • Output: the positions (indices) of the two numbers that add up to target.
  • You can’t use the same item twice.

Example: nums = [12, 35, 7, 15, 44], target = 50 gives [1, 3], because 35 + 15 = 50.

Try first, even if you get stuck. It helps you remember. Hints open one at a time:

Hint 1 of 3

Pick any price. Which exact price would its partner need to be?

Hint 2 of 3

For 35, the partner must be 50 − 35 = 15. So the question becomes: have I already seen 15?

Hint 3 of 3

"Have I seen this value before?" is a lookup. What data structure answers lookups quickly?

Watch the idea run, one step at a time:

One pass with a hash map
  1. 120
  2. 351
  3. 72
  4. 153
  5. 444

seen (value → index)

empty

Step 1 of 12. Start with an empty map. We want two numbers that add up to 50.

def two_sum(nums: list[int], target: int) -> list[int]:
    seen: dict[int, int] = {}  # value -> index
    for i, num in enumerate(nums):
        partner = target - num
        if partner in seen:
            return [seen[partner], i]
        seen[num] = i
    return []

The player stops to ask what happens next. Answer to test yourself, or press Just show me.

Every problem has three solutions: Okay, Better and Best. Each one shows the idea, the code, its cost, and where it falls short. You can also see exactly what changed from the rung before.

Other lessons climb the same way with their own rungs. A system design case study grows a design from v1 to v3. Scenario and interview lessons show a weak, a decent and a strong answer.

Okay

Check every pair

The idea: Try each number with every number after it.

Most people write this first, and it works. The problem: with 100,000 numbers, that’s about 5 billion checks.

def two_sum(nums: list[int], target: int) -> list[int]:
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
Time
O(n²)
Space
O(1)
Why
Two nested loops over the list; nothing extra is stored.
Better

Sort, then walk two pointers inward

The idea: Sort the values (keeping their original indices), then move one pointer from each end toward the middle.

Sum too small? Move the left pointer right. Too big? Move the right pointer left. The problem: sorting is extra work, and it’s easy to lose the original positions.

Highlighted lines are new in this rung.

def two_sum(nums: list[int], target: int) -> list[int]:
pairs = sorted((value, i) for i, value in enumerate(nums))
lo, hi = 0, len(pairs) - 1
while lo < hi:
total = pairs[lo][0] + pairs[hi][0]
if total == target:
return sorted([pairs[lo][1], pairs[hi][1]])
if total < target:
lo += 1
else:
hi -= 1
return []
Time
O(n log n)
Space
O(n)
Why
Sorting dominates; the pairs list holds a copy of every value and index.
What changed from the previous rung
def two_sum(nums: list[int], target: int) -> list[int]:
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
pairs = sorted((value, i) for i, value in enumerate(nums))
lo, hi = 0, len(pairs) - 1
while lo < hi:
total = pairs[lo][0] + pairs[hi][0]
if total == target:
return sorted([pairs[lo][1], pairs[hi][1]])
if total < target:
lo += 1
else:
hi -= 1
return []
Best

One pass with a hash map

The idea: Remember each number you've seen; for every new number, check whether its partner is already remembered.

When Best isn’t best: if the list is already sorted and memory is tight, use two pointers. That’s O(n) time and O(1) space.

Highlighted lines are new in this rung.

def two_sum(nums: list[int], target: int) -> list[int]:
seen: dict[int, int] = {} # value -> index
for i, num in enumerate(nums):
partner = target - num
if partner in seen:
return [seen[partner], i]
seen[num] = i
return []
Time
O(n) on average
Space
O(n)
Why
One loop, and each dictionary lookup is O(1) on average; the map can hold every number.
What changed from the previous rung
def two_sum(nums: list[int], target: int) -> list[int]:
pairs = sorted((value, i) for i, value in enumerate(nums))
lo, hi = 0, len(pairs) - 1
while lo < hi:
total = pairs[lo][0] + pairs[hi][0]
if total == target:
return sorted([pairs[lo][1], pairs[hi][1]])
if total < target:
lo += 1
else:
hi -= 1
seen: dict[int, int] = {} # value -> index
for i, num in enumerate(nums):
partner = target - num
if partner in seen:
return [seen[partner], i]
seen[num] = i
return []

Now write it in the page. Press Run tests to check your code. Python runs in your browser, so nothing is sent anywhere, and your code is saved here for next time.

Try the Okay version first. It passes the small tests but is too slow for the big extra one.

System design lessons call this part Draw it yourself: you build the design on a board instead of writing code.

Two Sum

Ctrl + Enter runs

Loads about 6 MB the first time. Best on Wi-Fi.

  1. Not run yet: two_sum([2, 7, 11, 15], 9) → [0, 1]
  2. Not run yet: two_sum([3, 2, 4], 6) → [1, 2]
  3. Not run yet: two_sum([3, 3], 6) → [0, 1]
  4. Not run yet: Extra test
  5. Not run yet: Extra test
  6. Not run yet: Extra test

Extra tests check tricky cases. You see their input only if they fail.

Try your own input
RungTimeSpaceWork at n = 100,000
OkayO(n²)O(1)≈ 5,000,000,000 pair checks
BetterO(n log n)O(n)≈ 1,700,000 sort comparisons
BestO(n)O(n)≈ 100,000 lookups

Each lesson ends with what to say out loud: questions to ask first, a 60-second walkthrough, and likely follow-ups. For Two Sum:

“The brute force checks every pair, which is O(n²). The waste is that each number already tells me its partner: target minus the number. If I keep the numbers I’ve seen in a hash map, each check is O(1). So one pass gives O(n) time and O(n) space. I’ll test [3, 3] with target 6 to make sure I never reuse an element.”

Three to five related problems, from easy to hard. Each has hints, an editor where code fits, and its own Okay, Better and Best answers. Some lessons use v1 to v3, or weak to strong, instead. Two Sum’s are a pair sum in a sorted list, counting pairs with a given difference, Three Sum and Four Sum.

A few quick questions. Ones you miss are remembered, so you can see your weak spots later.

  1. Predict the outputWhat does the Best version return?
    two_sum([3, 2, 4], 6)
  2. Multiple choiceWhy does the Best version check the map before inserting the current number?
  3. Fill in the answerFill in the blank: partner = target - ____

Then add the lesson’s cards to your deck. Each card comes back a little later every time you get it right.

Three to five points worth remembering next week. For Two Sum:

  • Need a partner or complement → a hash map of seen values.
  • Sorted input plus a pair condition → two pointers.
  • Check before you insert.

Links to the official docs and related lessons. Then Can you do these now? lists the lesson’s outcomes again, so you can tick off what you can do, and go back to anything you’re not sure of yet.

Mark the lesson complete when you finish. Already know it? Say so, and the roadmap shows it as known. It’s saved in this browser, and you can undo it.

Status: Not started

That’s every part. See them all in a real lesson: Two Sum, with practice problems. Or pick a track from the home page and start wherever suits you.