This lesson introduces recursion through the countdown function, uses a stack diagram to show why each recursive call has its own variables, explains infinite recursion and the base case that prevents it, and adds keyboard input with the conversion its result usually needs.
Subject: Python · 65 slides · code lesson
Open the interactive version of this deck
Title
Python · Chapter 5 — Conditionals and recursion
§5.8-5.12, pp. 43-46
Objectives
Five things, each one you can check yourself at an interpreter prompt.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 43-46 — the pages these objectives are drawn from
Warm-up
You know a function can call another function. Ask the obvious next question.
Discussion prompt
Nothing you have learned forbids a function from calling itself. Before reading on, predict what would happen — and say what would have to be true for it ever to stop.
Hint: Lesson 3b's boundary probe asked exactly this.
Answer:
It is legal, and each call is an ordinary call: a detour that runs the body and comes back. Nothing special happens at the moment of calling.
For it to stop, some call has to reach a point where it does NOT call itself again. Otherwise the detours never end.
That escape is called the base case, and getting it right is the whole difficulty of recursion. Everything else follows from rules you already have.
Concept
It is legal for one function to call another; it is also legal for a function to call itself. It may not be obvious why that is a good thing, but it turns out to be one of the most magical things a program can do.
recursion — The process of a function calling itself.
Nothing about the mechanism is new. Every time a function gets called, Python creates a frame to contain the function's local variables and parameters — and for a recursive function there might be more than one frame on the stack at the same time. That single fact is what makes recursion work.
Figure (svg): A stack diagram showing four countdown frames each with a different value of n, from three down to zero
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 43-44 — figure 5.1
Section
Section 1
Concept
If n is zero or negative, countdown outputs the word Blastoff. Otherwise it outputs n and then calls a function named countdown — itself — passing n minus one as an argument.
def countdown(n):
if n <= 0:
print('Blastoff!')
else:
print(n)
countdown(n-1)| Part | What it is | Why it matters |
|---|---|---|
| n <= 0 | the base case | print Blastoff and stop |
| else | the recursive case | print n, then call itself |
| n-1 | a smaller argument each time | so the base case is approached |
The structure is an if-else with two branches, and the two branches have names worth knowing: the branch that does not call itself is the base case, and the branch that does is the recursive case. Every recursive function has both.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 43-43
Picture it
The condition decides whether this call is the last one.
Figure (svg): A flow chart showing the countdown function testing n, printing Blastoff in one branch and printing n and calling itself in the other
The recursive call is on the right-hand exit, and it is the only thing making this different from an ordinary if statement. Everything else you already know.
Worked example
Four calls, and the output arrives on the way down. Follow the book's own trace.
>>> countdown(3)
3
2
1
Blastoff!| Call | What it does | Output |
|---|---|---|
| countdown(3) | 3 is not <= 0, so print 3 and call countdown(2) | 3 |
| countdown(2) | 2 is not <= 0, so print 2 and call countdown(1) | 2 |
| countdown(1) | 1 is not <= 0, so print 1 and call countdown(0) | 1 |
| countdown(0) | 0 IS <= 0, so print Blastoff and return | Blastoff! |
Start with n equal to 3.
Why: The execution of countdown begins with n=3, and since n is greater than 0, it outputs the value 3 and then calls itself.
Notice that each call is an ordinary call.
Why: It is a detour: the flow jumps into the body, runs it, and will come back. Nothing about calling the same function changes that.
Follow it down to the base case.
Why: Each call receives one less than the one before, so after four calls n is 0 and the base case runs, printing Blastoff and returning without calling anything.
Follow the returns back up.
Why: The countdown that got n=1 returns, then the one that got 2, then the one that got 3, and then you are back in __main__.
Figure (svg): The state of the program after each line of Worked example tracing countdown 3 , drawn as a ladder with one rung per traced line
Four lines: 3, 2, 1, Blastoff. The numbers are printed on the way down, one per call, and the returns produce no further output.
Verify: Count the calls and the lines of output.
Why: Four calls and four lines, one print per call. That correspondence is worth checking because it confirms the output comes from the descent rather than the return — if a line were printed AFTER the recursive call, the counts would still match but the ORDER would be reversed, which is the next worked example.
Prediction
Two calls. Trace both.
>>> countdown(1)| Call | Which branch | Output |
|---|---|---|
| countdown(1) | 1 is not <= 0 | print 1, call countdown(0) |
| countdown(0) | 0 IS <= 0 | print Blastoff, return |
| total | two calls | two lines |
Predict first
What does countdown(1) print?
Correct: 1 and then Blastoff! — two calls, one line each.
Why: The first call prints 1 and recurses with 0; the second takes the base case and prints Blastoff. Zero is never printed, because the base case prints Blastoff instead of n. That is a deliberate feature of the design: the base case does something different, which is what makes it a base case rather than merely the last recursive step.
Worked example
Move one line and the output reverses. Work out why.
def countup(n):
if n <= 0:
print('Blastoff!')
else:
countup(n-1)
print(n)| Stage | What happens | Output |
|---|---|---|
| countup(3) | calls countup(2) FIRST, before printing | nothing yet |
| countup(0) | the base case, at the bottom | Blastoff! |
| returns to countup(1) | now it prints | 1 |
| returns to countup(3) | each level prints on the way back | 2, then 3 |
Notice the only change.
Why: The print and the recursive call have swapped places. Nothing else differs.
Follow the descent.
Why: Each call immediately calls itself again, printing nothing, until the base case is reached. All four calls are on the stack before any output appears.
Follow the ascent.
Why: As each call returns, the print statement after its recursive call finally runs — and it runs for the deepest level first.
Figure (svg): Two columns comparing output printed before the recursive call with output printed after it
Blastoff, then 1, 2, 3. Moving one line reverses the output, because the printing now happens after the recursive call returns rather than before it is made.
Verify: Check where the output happens relative to the deepest call.
Why: In countdown all four numbers appear before Blastoff; in countup they all appear after it. That is direct evidence for which side of the recursive call the print statement is on — and it is why before or after the recursive call is the first thing to look at in any recursive function.
Trap
A student writes the recursive call as countdown(n) rather than countdown(n-1).
Copy the shape of the recursion without the shrinking argument
Why: The call looks right, and the base case is present and correct.
Every call receives the same n, so the condition never becomes true and the base case is never reached. The program prints the same number until Python stops it.
Every recursive call must move toward the base case.
Check that the argument changes, and changes in the right direction
Why: n-1 moves toward zero; n+1 moves away from it and is just as bad as no change.
Ask whether the base case is guaranteed to be reached
Why: For countdown with a whole number, subtracting one repeatedly always reaches zero. For a float or a negative step, it may not.
This is the first of the two questions the book gives for diagnosing infinite recursion, and it catches most cases. The other is whether a base case exists at all.
Matching
Every recursive function has these four parts.
Match the pairs
Why: Four parts, and three of them can go wrong independently. A missing base case gives infinite recursion; a condition that never becomes true gives the same; and an argument that does not shrink gives it again. Being able to name the parts is what lets you check them one at a time when a recursion misbehaves.
Prediction
A negative argument. Check the condition carefully.
Predict first
What does countdown(-1) print?
Correct: Blastoff! — the condition is n <= 0, and -1 satisfies it, so the base case runs immediately.
Why: The condition is less than or equal to zero rather than equal to zero, and that choice is what makes negative arguments safe. Had it been written n == 0, then countdown(-1) would recurse to -2, -3 and so on, never hitting the base case — an infinite recursion caused by a base case that exists but is unreachable from that starting point.
Socratic
In countdown it does. Work out what that buys.
Discussion prompt
In countdown, the recursive call is the last statement in the body. Describe what would happen if there were another print statement after it, and say what that tells you about when a recursive call returns.
Hint: It would run once per level, on the way back up.
Answer:
It would run once per level, after that level's recursive call had finished — so you would get the countdown on the way down and something else on the way back up.
That is exactly what countup demonstrated, with the two statements in the other order. The recursive call is an ordinary call: it returns, and execution continues after it.
The consequence worth remembering: a recursive function can do work on the way down, on the way back up, or both. Choosing which is a design decision, and it is the difference between printing 3, 2, 1 and printing 1, 2, 3 from almost identical code.
Section
Section 2
Concept
The same kind of stack diagram used in chapter 3 helps interpret a recursive function. Every time a function gets called, Python creates a frame to contain the function's local variables and parameters — and for a recursive function, there might be more than one frame on the stack at the same time.
# countdown(3), at the moment the base case runs:
# __main__ (empty)
# countdown n = 3
# countdown n = 2
# countdown n = 1
# countdown n = 0 <- the base case| Frame | State | Note |
|---|---|---|
| __main__ | empty | no variables were created there |
| countdown n=3 | paused, waiting for its call to return | still holds its own n |
| countdown n=0 | currently running | the base case |
The four countdown frames have different values for the parameter n. The bottom of the stack, where n is 0, is called the base case: it does not make a recursive call, so there are no more frames.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 44-44
Picture it
The same parameter name in four frames, holding four different values.
Figure (svg): A stack diagram with an empty main frame and four countdown frames holding n equal to three, two, one and zero
This is why lesson 3c insisted that a frame belongs to a CALL rather than to a function. Recursion is the case that makes the distinction unavoidable.
Worked example
Ask what would break if they shared. The answer is everything.
# suppose all four calls shared one n:
# countdown(3) sets n = 3, prints 3
# calls itself, which sets n = 2, prints 2
# ... down to n = 0, prints Blastoff
# returns to the call that had n = 3 -- but n is now 0| Design | What happens to n | Result |
|---|---|---|
| with separate frames | each call's n is untouched by deeper calls | correct |
| with a shared n | the deepest call overwrites everybody's n | broken |
| what breaks | any work after the recursive call | uses the wrong value |
Notice that countdown itself would survive.
Why: It does nothing after the recursive call, so it never looks at n again and would not notice the corruption.
Try it on countup instead.
Why: countup prints n AFTER the recursive call. With a shared n, every level would print whatever the deepest call left behind — the same number four times.
Conclude what locality buys.
Why: Each call keeps its own parameters and local variables, so work after a recursive call sees the values that call started with.
Figure (svg): The state of the program after each line of Worked example why each call keeps its own n, drawn as a ladder with one rung per traced line
Sharing would break any recursive function that does work after its recursive call — which is most of them. Separate frames are what make the value a call started with still available when it resumes.
Verify: Check the claim against lesson 3c's socratic probe.
Why: That probe asked what would go wrong if local variables survived after a function returned, and the answer given was that a function calling itself could not work at all. This is the same fact from the other side: it is not merely that old values are cleaned up, it is that concurrent calls are kept apart.
Invariant
Step through and watch the stack grow and then shrink.
Step through it
At the deepest point, how many frames hold a variable called n — and do any two of them hold the same value?
Four frames hold an n, with the values 3, 2, 1 and 0 — all different, because each was created by a separate call with a separate argument. If any two had shared a value, something would be wrong with the shrinking step.
Worked example
Draw the stack for print_n with a string and a count of two.
def print_n(s, n):
if n <= 0:
return
print(s)
print_n(s, n-1)| Call | What it does | Its frame |
|---|---|---|
| print_n('Hello', 2) | prints Hello, calls print_n('Hello', 1) | frame 1: s='Hello', n=2 |
| print_n('Hello', 1) | prints Hello, calls print_n('Hello', 0) | frame 2: s='Hello', n=1 |
| print_n('Hello', 0) | n <= 0, so return immediately | frame 3: s='Hello', n=0 |
Note that this function has two parameters.
Why: Each frame holds both, so s appears three times with the same value and n appears three times with different ones.
Count the frames.
Why: Three calls, so three frames plus __main__. The base case is the one with n equal to 0.
Count the output.
Why: It displays s and then calls itself to display s n minus one additional times, so the number of lines is 1 plus n minus 1, which adds up to n. Two lines for n equal to 2.
Figure (svg): A stack diagram for print_n called with Hello and two, showing three frames each holding s and n
Four frames including __main__, with n taking the values 2, 1 and 0, and s holding the same string in all three. Two lines of output, because only the two non-base calls print.
Verify: Check the line count formula against a different n.
Why: For n equal to 5 the formula predicts five lines, and tracing gives five calls that print plus one base case that does not. A formula that predicts correctly for a value you did not use to derive it is worth trusting.
Trap
Asked to draw the stack for countdown(3), a student draws a single countdown frame and crosses out n as it changes from 3 to 2 to 1.
Treat the parameter as a variable being updated
Why: It looks like a loop counter, and in a loop that is exactly what happens.
It is not a loop. Four calls are alive at once, each with its own n, and three of them are paused waiting for the fourth. A single frame cannot show that.
One frame per call, all present at the same time.
Add a frame each time a call is made, and remove it when that call returns
Why: Exactly the rule from lesson 3c, with several frames happening to name the same function.
Give each frame its own copy of every parameter
Why: That is what makes the values independent, and it is the thing the diagram exists to show.
This distinction is the whole reason the book returns to stack diagrams here. For non-recursive code you can get away with the sloppy version; for recursion you cannot.
Prediction
Count the calls, and do not forget __main__.
Predict first
When countdown(5) reaches its base case, how many frames are on the stack?
Correct: Seven — __main__ plus six countdown frames, for n equal to 5, 4, 3, 2, 1 and 0.
Why: The argument runs from 5 down to 0 inclusive, which is six values, so six calls are made and none has returned yet. Adding __main__ gives seven. The commonest error is to count five, forgetting both the base-case call and the __main__ frame — and lesson 3c made the same point, that __main__ is a frame like any other.
Faded example
The return statement exits the function immediately.
Fill in the blanks
def print_n(s, n):
if n <= 0:
return
print(s)
print_n(s, n-1)
Why: If n is zero or less, the return statement exits the function: the flow of execution immediately returns to the caller, and the remaining lines do not run. That is why the print and the recursive call can sit outside the if without an else — the return has already prevented them from being reached in the base case. This is a common recursive shape and it is worth recognising as an alternative to the if-else form countdown uses.
Explain it to yourself
Name its job precisely.
Discussion prompt
Explain what a base case is and what would happen without one, using the words frame and stack in your answer.
Hint: The bottom of the stack is where it lives.
Answer:
The base case is the branch that does NOT make a recursive call, so it is the bottom of the stack — the frame with no frame below it.
Without one, every call adds a frame and none ever stops adding, so the stack grows without limit and the program never terminates.
Python stops it at a thousand frames with a RuntimeError, but the point is that the base case is what makes a recursion finite. Everything else about a recursive function can be right and it will still never finish.
Section
Section 3
Concept
If a recursion never reaches a base case, it goes on making recursive calls forever, and the program never terminates. This is known as infinite recursion, and it is generally not a good idea.
def recurse():
recurse()| Call | What happens | Effect on the stack |
|---|---|---|
| recurse() | calls itself | a second frame |
| recurse() | calls itself again | a third frame |
| ... | no base case exists at all | frames keep accumulating |
| at 1000 frames | Python gives up | RecursionError |
In most programming environments a program with infinite recursion does not really run forever. Python reports an error when the maximum recursion depth is reached — and when the error occurs, there are a thousand recurse frames on the stack.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 44-45
Picture it
The traceback is the stack, printed — so an infinite recursion produces an enormous one.
Figure (svg): A traceback showing the same line repeated many times followed by a maximum recursion depth error
The repetition is the diagnostic. An ordinary traceback lists different functions; this one lists the same line over and over, which points straight at a missing or unreachable base case.
Worked example
The book gives two checks. Each catches a different failure.
# failure 1: no base case at all
def recurse():
recurse()
# failure 2: a base case that cannot be reached
def countdown(n):
if n == 0:
print('Blastoff!')
else:
print(n)
countdown(n-1)| Case | What is wrong | Diagnosis |
|---|---|---|
| failure 1 | there is no branch that avoids recursing | always recurses |
| failure 2 | a base case exists, but n == 0 misses negatives | countdown(-1) never stops |
| the checks | does a base case exist? is it reachable? | two separate questions |
Apply the first check.
Why: Review your function to confirm that there is a base case that does not make a recursive call. In failure 1 there is none — the body is a single recursive call.
Apply the second check.
Why: If there is a base case, check whether you are guaranteed to reach it. In failure 2 there is one, and countdown(-1) sails straight past it, because -1 is not equal to 0 and subtracting one moves further away.
Note the fix for the second.
Why: Testing n <= 0 rather than n == 0 makes the base case reachable from any starting value, which is why the book writes it that way.
Figure (svg): The state of the program after each line of Worked example the two ways a recursion fails to stop, drawn as a ladder with one rung per traced line
Two separate failures. One has no base case; the other has one that some inputs never reach. The two checks are does a base case exist and am I guaranteed to reach it, and both are needed.
Verify: Test the second version with a positive and a negative argument.
Why: countdown(3) works and countdown(-1) recurses forever. A function that works for the inputs you tried and not for others is the characteristic shape of an unreachable base case — which is why the second check asks about a guarantee rather than about a single trial.
Prediction
There is a base case. Check whether every input reaches it.
def halve(n):
if n == 1:
print('done')
else:
print(n)
halve(n // 2)| Input | What happens | Verdict |
|---|---|---|
| halve(8) | 8, 4, 2, then 1 | terminates |
| halve(0) | 0 // 2 is 0 | never changes — infinite |
| the flaw | the base case is n == 1 | zero never reaches it |
Predict first
For which inputs does this fail to terminate?
Correct: Zero — floor-dividing zero by two gives zero, so the argument never changes and the base case of 1 is never reached.
Why: This is the second kind of failure: a base case exists and is correct for most inputs, and one particular value never approaches it. It is worth noticing that the shrinking step failed rather than the condition — n // 2 usually decreases and for zero it does not. Negative numbers fail too, for the same reason in a different direction. Widening the base case to n <= 1 fixes both.
Worked example
It is longer than any traceback you have seen. Extract the two useful facts.
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded| What you see | What it means | What it tells you |
|---|---|---|
| the repeated line | one entry per frame | which function is recursing |
| the same line number | every entry | where the recursive call is |
| the error name | maximum recursion depth exceeded | what kind of failure |
Read the last line first, as always.
Why: Maximum recursion depth exceeded. That names the failure and immediately points at the base case.
Notice that every entry is identical.
Why: Same file, same line, same function. This traceback is a little bigger than the one seen in the previous chapter — a thousand frames rather than three.
Extract the location.
Why: The repeated line number is where the recursive call is, so that is the call that never stops. The function to inspect is named right there.
Figure (svg): The state of the program after each line of Worked example reading the recursion traceback, drawn as a ladder with one rung per traced line
Two facts: which function is recursing without end, and which line makes the call. From there the two checks apply — is there a base case, and can it be reached?
Verify: Compare with an ordinary traceback from lesson 3c.
Why: An ordinary traceback lists a chain of different functions, each called by the one above. This one lists one function a thousand times. The shape of the traceback alone identifies the problem before you read a word of it, which is a genuinely useful thing to be able to recognise.
Trap
A student checks that their recursive function has a base case, confirms it is there, and concludes the recursion must terminate.
Check for existence rather than reachability
Why: The book's first question is exactly this one, and it is the easier of the two.
A base case that some inputs sail past is no better than none for those inputs. countdown with n == 0 has a perfectly good base case that negative arguments never meet.
Ask both questions, and the second is the harder one.
Confirm the base case exists
Why: There must be a branch that does not make a recursive call.
Confirm every input reaches it
Why: Consider the extremes: negatives, zero, a very large value, a non-integer. Which of them approach the base case, and which do not?
The book states both: review your function to confirm that there is a base case that does not make a recursive call, and if there is a base case, check whether you are guaranteed to reach it.
Sorting
Check both questions for each.
Sort into buckets
For a positive integer argument, does each of these terminate?
Error analysis
Mark the two things wrong with this function.
Annotate
Two independent faults with one symptom is the reason the book gives two separate checks rather than one.
Edge cases
Python stops at a limit rather than letting the program run forever.
Discussion prompt
Python raises an error at about a thousand frames rather than continuing. Name one advantage of having a limit and one thing it means you cannot do.
Hint: Think about what a thousand frames cost, and about a legitimate deep recursion.
Answer:
The advantage is that a runaway recursion fails quickly with a clear message instead of consuming memory until something worse happens. The error names the problem exactly.
What it costs is depth: a legitimate recursion more than about a thousand levels deep will hit the same error even though nothing is wrong with it. Processing a list of ten thousand items recursively would fail.
So the limit encodes a judgement — that recursion beyond a thousand levels is far more often a bug than a design. For the genuinely deep cases the usual answer is to use a loop, which is why the book says that for simple examples it is probably easier to use a for loop.
Section
Section 4
Concept
The programs written so far accept no input from the user; they just do the same thing every time. Python provides a built-in function called input that stops the program and waits for the user to type something.
>>> text = input()
What are you waiting for?
>>> text
'What are you waiting for?'| Step | What happens | Note |
|---|---|---|
| input() | the program stops and waits | nothing happens yet |
| the user types and presses Enter | the program resumes | input returns |
| what it returns | what the user typed, as a string | always a str |
When the user presses Return or Enter, the program resumes and input returns what the user typed as a string. That last word is the one that matters most: it is always a string, whatever the user typed.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 45-45
Picture it
Lesson 1a named five kinds of instruction. This is the one that has been missing.
Figure (svg): A diagram showing a prompt being displayed, the program waiting, and a string being returned
Everything the user types arrives as text, including things that are obviously numbers. That is why the conversion functions from lesson 3a matter now.
Worked example
input can take a prompt as an argument. Look at the last two characters of it.
>>> name = input('What...is your name?\n')
What...is your name?
Arthur, King of the Britons!
>>> name
'Arthur, King of the Britons!'| Part | What it does | Note |
|---|---|---|
| the argument | the prompt to display | shown before waiting |
| the backslash-n | a newline character | moves to the next line |
| what is returned | only what the user typed | the prompt is not included |
Give input a prompt.
Why: Before getting input from the user it is a good idea to print a prompt telling them what to type, and input can take one as an argument.
Read the escape sequence.
Why: The sequence backslash-n at the end of the prompt represents a newline, which is a special character that causes a line break. That is why the user's input appears below the prompt.
Note what comes back.
Why: Only what the user typed. The prompt was displayed, not returned, so name holds the answer alone.
Figure (svg): The state of the program after each line of Worked example a prompt, and the newline in it, drawn as a ladder with one rung per traced line
The prompt appears, the newline moves to the following line, the user types there, and input returns exactly what they typed as a string.
Verify: Remove the newline and compare.
Why: Without it the cursor stays on the prompt's line, so the user types immediately after the question mark. Both are legal; the difference is purely cosmetic, which makes it a clean demonstration of what the escape sequence does.
Prediction
The user typed digits. Decide what came back.
Predict first
The user types 42 in response to input(). What is the type of the returned value?
Correct: str — input always returns what the user typed as a string, whatever the characters are.
Why: This is stated without exception: input returns what the user typed as a string. It does not inspect the characters and it does not guess. That consistency is what makes it predictable — a function whose return type depended on what a user happened to type would be impossible to write code against, which is the same argument lesson 1b made about division always producing a float.
Worked example
input returns a string. If you want a number you must say so.
>>> prompt = 'What...is the airspeed velocity of an unladen swallow?\n'
>>> speed = input(prompt)
42
>>> int(speed)
42
>>> speed + 1
TypeError: can only concatenate str (not "int") to str| Expression | What it is | Result |
|---|---|---|
| speed | what input returned | the STRING '42' |
| int(speed) | converted | the number 42 |
| speed + 1 | a str and an int | TypeError |
Notice the type of what came back.
Why: A string, even though the characters are digits. This is lesson 1b's central point arriving in real code.
Convert if you expect a number.
Why: If you expect the user to type an integer, you can try to convert the return value to int.
See what happens without the conversion.
Why: Arithmetic on the string fails with the TypeError you met in lesson 1b, and comparison with a number silently gives False.
Figure (svg): A diagram showing text arriving from the keyboard, being converted with int, and then usable in arithmetic
input returns the string '42'. Converting with int gives the number 42; using it without converting fails, or worse, silently compares unequal to every number.
Verify: Check what speed == 42 gives before the conversion.
Why: False, with no error at all. That is the more dangerous of the two failure modes: the arithmetic version stops the program and the comparison version quietly takes the wrong branch — which is precisely the trap from lesson 5a about comparing a number with a string that looks like one.
Trap
A program asks for a number, converts the answer with int, and assumes it will work.
Design for the cooperative user
Why: In testing, you type 42 every time, and it works every time.
A user who types anything else — a word, an empty line, a decimal — produces a ValueError and the program stops. The book's own example has the user answering with a question about swallows.
Expect the input not to be what you asked for.
Convert deliberately, and know that it can fail
Why: int on a string raises ValueError when the characters do not spell an integer, exactly as lesson 3a showed.
For now, at least know where the failure will occur
Why: The book says: we will see how to handle this kind of error later.
That later is chapter 14, which introduces catching exceptions. Until then the honest position is that these programs assume cooperative input — and knowing that you are assuming it is most of the value.
Discrimination
Ask whether the value is being used as text or as a number.
Sort into buckets
For each use of a value returned by input, is a conversion needed?
Prediction
No conversion has happened. Apply lesson 5a's rule.
answer = input('Enter a number: ')
# the user types 42
print(answer == 42)| Value | Its type | Result |
|---|---|---|
| answer | the string '42' | a str |
| answer == 42 | a str compared with an int | never equal |
| output | False | no error |
Predict first
What does this print when the user types 42?
Correct: False — the string '42' is not equal to the number 42, and no error is raised.
Why: This is the most dangerous shape of the input-returns-a-string fact. Unlike arithmetic, which raises a TypeError and stops you, comparison quietly answers no. A program written this way takes the wrong branch for every input, including the one the user was asked for, and nothing at all indicates why. The fix is int(answer) == 42, converting at the point where the text arrives.
Notation
The backslash-n in a prompt is the first escape sequence in the book.
Annotate
Escape sequences are how you put characters into a string that you cannot type directly — a line break being the obvious first case.
Section
Section 5
Concept
When a syntax or runtime error occurs, the error message contains a lot of information but can be overwhelming. The most useful parts are usually what kind of error it was, and where it occurred — and the second of those is less reliable than it looks.
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent| Aspect | What is true | Note |
|---|---|---|
| the problem | the second line is indented by one space | invisible |
| where the caret points | at y | not at the space |
| the lesson | the message says where it was DISCOVERED | not always where it is |
Whitespace errors can be tricky because spaces and tabs are invisible and we are used to ignoring them. In general, error messages indicate where the problem was discovered, but the actual error might be earlier in the code, sometimes on a previous line.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 46-46
Picture it
The two are often the same line. When they are not, this is the shape.
Figure (svg): A panel showing an error reported on one line with an arrow indicating the actual cause on the previous line
This is why the standard advice for a syntax error is to look at the line reported and the line before it. Unclosed brackets and quotes are discovered late by their nature.
Worked example
The book's own example. The message points at the wrong thing.
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent| Aspect | What is true | Note |
|---|---|---|
| the cause | one leading space on the second line | invisible on screen |
| the caret | points at the y | the first visible character |
| the fix | remove the space | not anything to do with y |
Read the error kind first.
Why: IndentationError, unexpected indent. The name already tells you it is about whitespace rather than about the statement.
Notice where the caret points.
Why: At y — the first character it could point at. But the problem is the space BEFORE the y, which has no visible position of its own.
Take the general lesson.
Why: Error messages indicate where the problem was discovered, and the actual error might be earlier, sometimes on a previous line.
Figure (svg): The state of the program after each line of Worked example the misleading indentation error, drawn as a ladder with one rung per traced line
The cause is a single leading space, and the message points at the first visible character after it. Reading the error KIND rather than only the position is what identifies it.
Verify: Check that the statement itself is fine.
Why: y = 6 typed with no leading space works perfectly. The statement was never the problem, which confirms that the caret's position was about where the parser stopped rather than about what was wrong.
Prediction
The message points at line 5. Decide whether to look there.
line 4: total = (a + b
line 5: print(total)
SyntaxError: invalid syntax # reported on line 5| Line | What is true | Role |
|---|---|---|
| line 4 | an opening bracket with no closer | the cause |
| line 5 | reads as a continuation of line 4 | where it fails |
| the report | line 5 | where it was discovered |
Predict first
Which line contains the actual mistake?
Correct: Line 4 — the opening bracket is never closed, and Python only discovers this when line 5 fails to continue the expression sensibly.
Why: Because the bracket is open, Python treats the next line as a continuation of the expression, and only complains when what it finds cannot possibly fit. The error is discovered on line 5 and caused on line 4. This is the commonest instance of the rule, and it is why check the previous line is standard advice for a syntax error that makes no sense.
Worked example
The book says the most useful parts are the kind and the place. Use both.
Traceback (most recent call last):
File "prog.py", line 12, in <module>
average = total / count
ZeroDivisionError: division by zero| Question | The answer | What to do |
|---|---|---|
| what kind | ZeroDivisionError | a value was zero |
| where | line 12, the division | where it broke |
| what next | why was count zero? | look upstream |
Answer the first question: what kind of error?
Why: ZeroDivisionError. That narrows it to a division whose divisor was zero — not a typo, not a missing name, not a type problem.
Answer the second: where did it occur?
Why: Line 12, and the line is shown so you do not have to open the file.
Then ask the question the message cannot answer.
Why: Why was count zero? The traceback tells you where the program broke, and finding out why means looking at what set count.
Figure (svg): The state of the program after each line of Worked example applying the two questions, drawn as a ladder with one rung per traced line
The kind identifies what class of thing went wrong and the place identifies where to start looking. Neither tells you the cause, which is why the next step is nearly always a print statement.
Verify: Check that the two questions really do narrow the search.
Why: Without them you would be reading a whole file. With them you are reading one line and asking one question about one variable. That reduction is exactly what the book means by saying these are the most useful parts of the message.
Trap
A syntax error is reported on line 12, so the student rewrites line 12 several ways until something changes.
Treat the reported position as the location of the mistake
Why: It usually is, and rewriting is quicker than reading.
When the cause is an unclosed bracket on line 11, no amount of rewriting line 12 helps — and each attempt makes the file more different from the version that was nearly correct.
Read the reported line and the one before it, and read the error KIND first.
Let the error kind narrow the possibilities
Why: An IndentationError is about whitespace; a SyntaxError near a line that looks fine suggests something unfinished above it.
Check that every opener on the previous line has its closer
Why: Brackets and quotation marks are discovered late by their nature, because Python keeps reading until the line stops making sense.
The general principle is the book's: error messages indicate where the problem was discovered, but the actual error might be earlier in the code.
Matching
The kind is the first useful part of the message.
Match the pairs
Why: The last two are worth telling apart precisely. int('hello') is a ValueError — a string is the right type of argument and those particular characters do not spell an integer. int([1,2]) is a TypeError — a list is the wrong kind of thing entirely. Knowing which you have tells you whether to look at the value or at where it came from.
Step zero
Before reading any code, what do you do?
Discussion prompt
You get an error message you have never seen before, on a program of forty lines. What are the first two things you extract from the message, and what do you do with each?
Hint: The book names both of them.
Answer:
First: what kind of error it was. The name at the start of the last line tells you what class of problem you are looking for, before you have looked at anything.
Second: where it occurred. The line number and the function name give you a starting point that is usually right and occasionally one line off.
What you do with them: use the kind to decide what to look FOR, and the place to decide where to look FIRST. If the reported line looks fine, widen to the line above — and if it is a runtime error rather than a syntax error, ask what value that line received rather than whether it is written correctly.
Real world
Reported location versus actual cause is a general property of diagnostics.
Discussion prompt
Think of a diagnostic message from outside programming — a car warning light, a form validation message, a spell checker. Describe a case where the thing reported was a symptom discovered downstream of the actual problem.
Hint: Warning lights are famous for this.
Answer:
A warning light for a sensor fault is the classic: the sensor is fine and the wiring to it is not, so the reported component is the place the problem was noticed.
Spell checkers do it too — an unclosed quotation mark makes everything after it look wrong, and the first flagged word is nowhere near the actual error.
The common structure is that a system reports the first place where it could no longer proceed, which is downstream of wherever things actually went wrong. That is why check just upstream of the reported location is a general debugging habit rather than a Python-specific one.
Comparison
Fill the blanks. Both repeat work; they differ in how they keep track.
Comparison matrix
| Question | A for loop | Recursion |
|---|---|---|
| How many frames? | one | one per level of depth |
| What stops it? | running out of items to loop over | reaching the base case |
| How does it go wrong? | a loop that never ends | a base case that is missing or unreachable |
| Which is easier here? | the loop, for simple examples like countdown | recursion, for problems the book meets later |
The book is honest about the last row: for simple examples like this it is probably easier to use a for loop, but there are examples later that are hard with a loop and easy with recursion.
Pattern
Five steps, and the first two are where the thinking is.
Step 3's advice about inequalities is what turns n == 0 into n <= 0, and it is the cheapest possible defence against an unreachable base case.
Python documentation — More Control Flow Tools More Control Flow Tools
Check
Count the calls, and remember what the base case prints.
def countdown(n):
if n <= 0:
print('Blastoff!')
else:
print(n)
countdown(n-1)| Call | What it does | Output |
|---|---|---|
| countdown(2) | prints 2, calls countdown(1) | 2 |
| countdown(1) | prints 1, calls countdown(0) | 1 |
| countdown(0) | base case | Blastoff! |
Check your understanding
What does countdown(2) print?
Answer: B
Why: Three calls are made, for n equal to 2, 1 and 0. The first two take the recursive branch and print their n; the third takes the base case and prints Blastoff instead. So the zero is never printed — the base case does something different, which is what makes it a base case.
Check
A base case exists. Ask whether every input reaches it.
def f(n):
if n == 0:
return
print(n)
f(n-2)| Input | What happens | Verdict |
|---|---|---|
| f(4) | 4, 2, then 0 | terminates |
| f(3) | 3, 1, -1, -3, ... | never equals 0 |
| the flaw | an equality test with a step of 2 | odd inputs miss it |
Check your understanding
For which inputs does this fail to terminate?
Answer: C
Why: The step subtracts two and the base case tests for exact equality with zero, so an odd starting value produces only odd values — 3, 1, -1, -3 — and never hits zero. The base case exists and is unreachable from half of the possible inputs, which is the second of the two failures the book describes. Testing n <= 0 instead would fix it.
Check
The user typed digits. Ask what type came back.
n = input('How many? ')
print(n * 2)| Value | What it is | Result |
|---|---|---|
| n | the string '5' | input always returns a str |
| n * 2 | a str repeated | '55' |
| what was wanted | 10 | needs int(n) first |
Check your understanding
The user types 5. What does this print?
Answer: B
Why: input returns the string '5', and the star operator applied to a string and an integer repeats the string — which is lesson 2b's repetition rule. So the output is the two characters 55, not the number 10. Getting 10 requires converting first: int(n) * 2.
Real world
Self-reference with a stopping rule is a shape you have seen before.
Discussion prompt
Find something outside programming that is defined in terms of itself but still terminates — a set of instructions, a definition, a physical process. What plays the role of the base case, and what would happen without it?
Hint: Russian dolls, and the instructions on a shampoo bottle.
Answer:
Russian dolls are the physical version: each contains a smaller one, until the smallest, which contains nothing. That innermost doll is the base case, and without it the sequence could not exist at all.
Lather, rinse, repeat is the joke version, and the joke is precisely that it has no base case.
Definitions do it too: an ancestor is a parent, or an ancestor of a parent. The base case is a parent, and without it the definition would define nothing. In every case the base case is what turns a self-reference from circular into constructive.
Commit first
Answer, then rate your confidence. This one is about what input returns.
Predict first
A program does answer = input() and the user types 7. What does answer == 7 give?
Correct: False — input returns a string, and a str is never equal to an int however similar they look.
Why: This combines two facts that are individually easy and jointly catch nearly everybody. input always returns a string, and comparing values of different types with the equality operator gives False rather than an error. The result is a condition that is silently always false, so the program takes the wrong branch for every input including the one that was asked for — with no message of any kind. The fix is int(answer) == 7, converting as soon as the text arrives.
Explain it
The stack diagram is what makes recursion explicable.
Discussion prompt
A classmate says they do not understand how a function can call itself without getting confused about which n is which. Draw them the picture and explain it in three sentences.
Hint: The answer is the frame, from lesson 3c.
Answer:
Draw the four countdown frames stacked, each with its own n. Say: every call gets its own frame containing its own copy of the parameters.
So the call with n equal to 3 is paused, still holding its 3, while the call with n equal to 2 runs. Nothing they do can affect each other's variables.
Then make the point that ties it back: this is the same locality rule as any other function call. The only thing that is new is that several frames happen to name the same function — which is why the frame belongs to the CALL rather than to the function.
Exit ticket
One honest answer. It decides what the next lesson opens with.
Predict first
Which of these is still least solid for you?
Correct: Whichever you picked is the right answer — this one is for you, not for a mark.
Why: Tracing gets easier with practice and the before-or-after-the-call question resolves most of it. The stack diagram is the one worth over-learning, because chapter 6's leap of faith section asks you to stop tracing and trust the recursion, which only works if you believe the frames are separate. Base cases will keep mattering for as long as you write recursive code, and the two-question check is worth memorising. And the input-returns-a-string fact is about to become constant — from here on, most programs that ask a question need a conversion on the very next line.
Connect it up
One page, from memory. The drawing is the exercise.
Draw it
Draw the full stack diagram for countdown(3) at the moment the base case runs, with every frame and every value of n. Beside it, write the output in the order it appears, and draw an arrow from each printed line to the frame that produced it. Then, underneath, write the two questions to ask when a recursion does not terminate, and give a one-line example of a function that fails each one.
Recap
Four pages, and one of the most powerful ideas in the book.
| If you remember one thing | It is this |
|---|---|
| From recursion | Each call gets its own frame. That is why the levels do not interfere. |
| From the base case | It must exist AND be reachable from every input. Prefer <= to == for the test. |
| From infinite recursion | A traceback repeating one line a thousand times means a base case problem. |
| From input | It always returns a string. Convert on the next line if you want a number. |
| From debugging | The message says where the problem was DISCOVERED, not always where it is. |
Chapter 6 makes functions fruitful: they will finally be able to return a value rather than only printing one, which is the missing piece that has made every function so far a dead end for its caller.
Think Python, 2nd edition — Allen B. Downey §5.8-5.12, pp. 43-46 — everything on these slides traces back here
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.