THE LEARNING ENGINE/ Validate at the boundary0%

Flagship sprint · Standard intensity · the full loop

Validate at the boundary

⬡ Sprint contractstandard · 60–90 min
Outcome
Write a function that fails fast on bad input, with a clear error and a test that proves it.
Proof required
A passing test, a deliberately-broken case you fixed, and a one-line teach-back.
Unlock standard
All verification items checked, transfer task attempted, review scheduled.

Don’t claim after reading: “I understand input validation” — until you’ve made a broken case pass.

01 MissionA real problem, not a topic

A teammate’s service crashed at 2am because a function got an empty list and divided by zero. Your job: make functions reject bad input at the boundary, loudly and early — so failures are obvious in development, not silent in production.

02 Where this fitsAnchor the skill to real work

This shows up everywhere real: API request handlers, data pipeline steps, library functions others call. “Validate at the boundary” is the habit that separates code that fails gracefully from code that corrupts state three layers deep.

03 PretestRetrieval & productive failure before reading

Before reading: what should average([]) do, and why is returning 0 a trap? Answer from memory.

04 Worked exampleSee a complete model first

Here is the complete, guarded version. Read it, then notice what each line defends against.

def average(values):
    if not values:
        raise ValueError("average() needs at least one value")
    return sum(values) / len(values)

print(average([2, 4, 6]))  # 4.0
  1. Guard first: the empty case is handled before any math runs.
  2. Raise, don’t return a sentinel — the error names the function and the requirement.
  3. The happy path stays a single, obvious line.

Common mistake: Returning None or 0 on bad input. The caller forgets to check, and the bad value flows downstream.

05 Concept capsuleThe smallest idea needed to act

Fail fast at the boundary

Validate inputs at the edge of a function (or a system), reject what violates the contract immediately, and make the error specific. The cost of a failure grows the further it travels from its cause — so stop it at the door.

⬡ Guided lab · in-browser

Build the guarded function

Implement average() so it raises ValueError on an empty list and returns the mean otherwise. Run it until the checkpoint passes.

Open lab →
07 Broken caseHandle failure, not just success

Symptom: This “fixed” version still crashes on average([]) with ZeroDivisionError instead of a clear message.

def average(values):
    total = sum(values)
    return total / len(values)  # crashes when values is empty

Spot why the guard is missing, then describe the one-line fix.

08 Tradeoff decisionChoose under constraints

Empty input: raise an exception, or return None?

09 VerificationProve the result — no vibes

Prove it — don’t assume it.

0/4 proofs

◇ Quick check

Why prefer raising over returning a sentinel like -1 for invalid input?

11 Teach-backExplain it to lock it in

Explain it out loud — to an AI, a reviewer, or the rubber duck. Cover each prompt with a concrete example and one edge case.

  • What does “validate at the boundary” mean, in one sentence?
  • Show a concrete before/after with a real function.
  • Name one edge case beyond the empty list (e.g. non-numeric values).
  • When would returning None actually be the right call?

Passing bar: average 4/5, nothing below 3, one concrete example, one edge case, one next improvement.

12 CalibrationCompare to weak / passing / excellent

Calibrate your failure-handling note against the bank.

“average() raises ValueError on [] with a named message; test asserts the raise and the happy path.” — correct, specific, verifiable.

Reviewer note: A score can’t exceed 4/5 without concrete evidence and one handled follow-up. “Passing” is the floor for unlock; “excellent” is what the calibration bank trains toward.

13 Transfer challengeUse the pattern somewhere new

Far transfer: take a function from your own code that trusts its input — a request handler, a parser, a config loader — and add one boundary guard with a named error and a test. Different domain, same pattern.

14 Spaced reviewReview over time, not in one cram
  1. Same day · recall without notes
  2. Next day · redo the pretest
  3. Day 3 · solve a similar task
  4. Day 7 · explain in interview format
  5. Day 14–30 · transfer to a new project
Unlock gateAdvance only with proof

Meet every criterion to unlock