Come Back Later: Review Challenges

The Course
•
September 24, 2026
•
6 min read

A week from now, some of this course will have faded. That’s normal, and it’s the reason for this page: trying to recall something that has half gone, and then checking, is one of the most reliable ways to make it last.

Bookmark this page. Come back in about a week for the first set and about a month for the second. Those gaps are a sensible starting point, not a magic schedule.

  • Try each one from memory first. Rereading a lesson before trying skips the part that does the work.
  • Use the hints to check. Each names the lesson it comes from.
  • Keep a list of the ones you needed help with. That’s your reading list — much shorter than the whole course.

A week later

1. Predict both lines. Two patterns that differ only in the order of their alternatives.

Week 1: predict Python
import re

print(re.findall(r'(\w+)=("[^"]*"|\S+)', 'a=1 msg="two words"'))
print(re.findall(r'(\w+)=(\S+|"[^"]*")', 'a=1 msg="two words"'))
Check your prediction
[('a', '1'), ('msg', '"two words"')]
[('a', '1'), ('msg', '"two')]

Alternation takes the first alternative that matches, and \S+ happily matches "two on its own. Put the most specific alternative first. From When a Regex Is the Right Tool.

2. Fix. This should find the user o'brien, and prints an SQL error instead.

Week 1: fix Python
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
conn.executemany("INSERT INTO users VALUES (?)", [("ada",), ("o'brien",)])

def find(name):
    return conn.execute(f"SELECT name FROM users WHERE name = '{name}'").fetchall()

print(find("o'brien"))
Check your fix
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
conn.executemany("INSERT INTO users VALUES (?)", [("ada",), ("o'brien",)])

def find(name):
    return conn.execute("SELECT name FROM users WHERE name = ?", (name,)).fetchall()

print(find("o'brien"))
[("o'brien",)]

The apostrophe ended the SQL string early. A ? placeholder sends the name as a value, which can never become SQL — the same fix that stops an injection. From Queries You Can Trust.

3. Write level_at_least(minimum), a factory that returns a filter. This should print ['WARNING', 'ERROR']:

Week 1: write Python
ORDER = ["DEBUG", "INFO", "WARNING", "ERROR"]

# def level_at_least(minimum): ...

keep = level_at_least("WARNING")
print([level for level in ORDER if keep(level)])
Check your answer
ORDER = ["DEBUG", "INFO", "WARNING", "ERROR"]

def level_at_least(minimum):
    def keep(level):
        return ORDER.index(level) >= ORDER.index(minimum)
    return keep

keep = level_at_least("WARNING")
print([level for level in ORDER if keep(level)])
['WARNING', 'ERROR']

The inner function remembers minimum after the factory has returned. From Functions That Build Functions.

4. Explain. No code: what’s the difference between a refactor and a fix, and why should they never happen in the same step?

Check your explanation

A refactor changes how code is organised and leaves what it does exactly the same; a fix changes what it does. Done together, a change in the output can’t be traced to either one — and the safety net can’t tell a mistake in the refactor from the intended fix. From Read Before You Change.

A month later

1. Predict both lines. The first is the repair for Day 4’s bug hunt.

Month 1: predict Python
import json

adders = [lambda x, i=i: x + i for i in range(3)]
print([add(10) for add in adders])
print(json.dumps({404: 2, "ok": True}))
Check your prediction
[10, 11, 12]
{"404": 2, "ok": true}

A default value is evaluated when each lambda is made, so each keeps its own i. And JSON keys are always strings — the integer 404 becomes "404". From Functions That Build Functions and Summaries as JSON.

2. Fix. A level saved to the database should come back as a Level: this should print ERROR True. It crashes.

Month 1: fix Python
import sqlite3
from enum import IntEnum

class Level(IntEnum):
    INFO = 20
    ERROR = 40

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (level INTEGER)")
conn.execute("INSERT INTO t VALUES (?)", (Level.ERROR,))

stored = conn.execute("SELECT level FROM t").fetchone()[0]
print(stored.name, stored is Level.ERROR)
Check your fix
import sqlite3
from enum import IntEnum

class Level(IntEnum):
    INFO = 20
    ERROR = 40

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (level INTEGER)")
conn.execute("INSERT INTO t VALUES (?)", (Level.ERROR,))

stored = Level(conn.execute("SELECT level FROM t").fetchone()[0])
print(stored.name, stored is Level.ERROR)
ERROR True

An IntEnum goes into SQLite as a plain number and comes back as one. Convert it back at the edge, where it’s read. From Checkpoint: Levels Everywhere.

3. Write collect(names): fetch every name at once, and report a failed fetch as "failed" instead of losing the rest. This should print {'web': 3, 'db': 'failed', 'api': 3}:

Month 1: write Python
import asyncio

async def fetch(name):
    await asyncio.sleep(0.01)
    if name == "db":
        raise ConnectionError("unreachable")
    return 3

# async def collect(names): ...

print(asyncio.run(collect(["web", "db", "api"])))
Check your answer
import asyncio

async def fetch(name):
    await asyncio.sleep(0.01)
    if name == "db":
        raise ConnectionError("unreachable")
    return 3

async def safely(name):
    try:
        return await fetch(name)
    except ConnectionError:
        return "failed"

async def collect(names):
    results = await asyncio.gather(*(safely(name) for name in names))
    return dict(zip(names, results))

print(asyncio.run(collect(["web", "db", "api"])))
{'web': 3, 'db': 'failed', 'api': 3}

Handle each source’s expected failure inside that source’s task, and gather keeps the order you asked in. From Timeouts and Failures.

4. Explain. No code: what makes a test able to fail? Give the two examples from Day 3.

Check your explanation

It has to use input that would expose the bug. A test of “results are sorted” needs records stored out of order — sorted input passes even with no sorting. A test of “a failed save stores nothing” needs the bad record after a good one — a bad first record passes with or without a transaction. From Testing with a Throwaway Database.

After that

If these came easily, build something: the briefs at the end of On Your Own are a good start. If some didn’t, you now know exactly which lessons to reread — and that list is worth more than a second pass through the whole course.

Last updated on September 24, 2026

Was this article helpful?

Your response is saved on this device.