Flagship sprint · Standard intensity · the full loop
Validate at the boundary
- 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.
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.
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.
Before reading: what should average([]) do, and why is returning 0 a trap? Answer from memory.
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- Guard first: the empty case is handled before any math runs.
- Raise, don’t return a sentinel — the error names the function and the requirement.
- 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.
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.
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.
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 emptySpot why the guard is missing, then describe the one-line fix.
Empty input: raise an exception, or return None?
Prove it — don’t assume it.
0/4 proofs
Why prefer raising over returning a sentinel like -1 for invalid input?
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.
Calibrate your failure-handling note against the bank.
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.
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.
- Same day · recall without notes
- Next day · redo the pretest
- Day 3 · solve a similar task
- Day 7 · explain in interview format
- Day 14–30 · transfer to a new project
Meet every criterion to unlock