Ab Semantic Errors: Making the Program Say What You Mean

This lesson covers the error kind that produces no message: forming a hypothesis about what a program is doing, breaking complex expressions into temporary variables, correcting a faulty mental model, and knowing when and how to ask for help.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson Ab Semantic Errors: Making the Program Say What You Mean

Title

Python · Appendix A — Debugging

§A.3 Semantic errors, pp. 198-200

2. By the end of this lesson you can

Objectives

Five things, each one you can check yourself at an interpreter prompt.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 198-200 — the pages these objectives are drawn from

3. Before we start: the error with no message

Warm-up

The other two kinds tell you something.

Discussion prompt

A program runs to completion and prints the wrong number. What information does the interpreter give you about what went wrong?

Hint: How much did it say?

Answer:

None at all. Every step was legal, so nothing was reported — the program did exactly what it says, and what it says is not what was meant.

Which means the usual first move is unavailable: there is no message to read and no line number to look at.

The book puts it precisely: the interpreter provides no information about what is wrong, because only you know what the program is supposed to do.

4. The one idea behind this lesson: only you know what it should do

Concept

In some ways, semantic errors are the hardest to debug, because the interpreter provides no information about what is wrong. Only you know what the program is supposed to do.

The first step is to make a connection between the program text and the behaviour you are seeing. You need a hypothesis about what the program is actually doing — and one of the things that makes that hard is that computers run so fast.

Figure (svg): Two columns contrasting what the interpreter supplies for other errors with what it supplies here

The interpreter provides no information about what is wrong.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 198-198

5. Forming a hypothesis

Section

Section 1

6. Three questions to ask yourself

Concept

The first step is to make a connection between the program text and the behaviour you are seeing. You need a hypothesis about what the program is actually doing.

Each question turns it doesn't work into something specific enough to test — which is what a hypothesis is, and it is what the interpreter would have given you for the other two kinds of error.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 198-198

7. Picture it: three shapes of wrongness

Picture it

Something missing, something extra, or something different.

Figure (svg): Three diagnostic questions with what each one directs you to check

Each question locates a section of code and asks something checkable about it.

And the third has its own remedy: read the documentation for the functions you call, and try them out by writing simple test cases and checking the results.

8. Worked example: from *it doesn't work* to a test

Worked example

The questions convert a complaint into a hypothesis.

# 'the total is wrong'

# question 1: is the adding code running at all?
print('adding', x)      # inside the loop

# question 2: is it running more often than it should?
# question 3: does sum do what I think on this input?
StageWhat you haveNote
the complaintnot testablethe total is wrong
a questionnarrows itwhich section?
a printanswers ita hypothesis, tested

Start from the observation.

Why: The total is wrong is a symptom, not a hypothesis — nothing about it can be tested.

Pick a question.

Why: Each one names a section of code and something checkable about it: is it running, is it running too often, does it do what you think.

Test it.

Why: A well-placed print answers the question, and the answer either confirms the hypothesis or eliminates it.

Figure (svg): The state of the program after each line of Worked example from it doesn't work to a test, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A testable claim in place of a complaint. That conversion is what the interpreter does for you with the other two error kinds, and here you have to do it yourself.

Verify: Note why the speed is a problem.

Why: One of the things that makes forming a hypothesis hard is that computers run so fast — the behaviour you want to observe is over before you can see it. A print is how you slow a moment down enough to look at, which is why the technique is so central here.

9. Predict: what does a semantic error give you?

Prediction

The program ran to completion.

# the program runs, prints a number,
# and the number is wrong
InformationAvailable?Note
a messagenonenothing was illegal
a locationnoneno exception
what you havethe outputand what it should have been

Predict first

What information does the interpreter provide?

  • None — only you know what the program is supposed to do
  • A message naming the wrong value
  • The line where the wrong value was computed
  • A warning, if the value is implausible

Correct: None — the interpreter provides no information about what is wrong.

Why: Every step was legal, so there was nothing to report. The reason is the one the book gives: only you know what the program is supposed to do, which is information the program does not contain — and it is why this kind of error needs a different approach entirely.

10. Worked example: prints against a debugger

Worked example

The book is pragmatic about this.

# 'You will often wish that you could slow the program
#  down to human speed, and with some debuggers you can.
#  But the time it takes to insert a few well-placed
#  print statements is often short compared to setting
#  up the debugger, inserting and removing breakpoints,
#  and stepping the program to where the error is.'
ToolWhat it costsNote
a debuggermore powerfuland more setup
a few printsless powerfuland immediate
the choiceby costnot by sophistication

Note what a debugger offers.

Why: The ability to slow the program down to human speed, which is exactly what you want.

Note what it costs.

Why: Setting it up, inserting and removing breakpoints, and stepping the program to where the error is occurring.

Note the comparison.

Why: The time to insert a few well-placed print statements is often short by comparison — so the simpler tool wins on total cost rather than on capability.

Figure (svg): Two columns weighing print statements against a debugger

The book's comparison is about total time rather than capability.

A recommendation based on effort rather than power. The word well-placed is doing work: a few prints in the right place beat many in the wrong one.

Verify: Ask when the debugger wins.

Why: When the program is large, the state is complex, or the same prints would have to be inserted and removed repeatedly — which is precisely when the setup cost is amortised. The book's earlier note mentions pdb for tracking down exceptions, since it lets you examine the state immediately before the error.

11. Trap: debugging without a hypothesis

Trap

The trap

A program gives the wrong answer, so changes are made and it is run again, repeatedly.

Try things until something works

Why: Each run feels like progress, and occasionally something changes.

Without a hypothesis, each run tests nothing — the book names this random walk programming, the attempt to program by writing every possible program and choosing the one that does the right thing.

The fix

Form a hypothesis, then test it.

Use the three questions to make one

Why: Something missing, something extra, or something different.

Then place a print that would confirm or eliminate it

Why: So the run produces information either way.

A run that could not have surprised you has told you nothing. The questions exist to produce claims specific enough that a single print settles them.

12. Discriminate: which question applies?

Discrimination

Three shapes of wrongness.

Sort into buckets

For each symptom, which of the three questions fits?

something supposed to happen is not
the confirmation message never appears; the totals line is missing from the report
something is happening that should not
the file is deleted when it should not be; a discount is applied twice
an effect that is not what you expected
the sort puts things in an order you did not expect; a library function returns something surprising
miss
Both are absences — find the section of code that performs that function and make sure it is executing when you think it should.
extra
Both are things happening that should not — find the code that performs that function and see if it is executing when it shouldn't.
diff
Both involve code doing something other than what you expected, especially functions in other modules — so read the documentation and try them out with simple test cases.

13. Complete it: the first step

Faded example

Before any technique.

Fill in the blanks

# you need a hypothesis about what the program
# is actually doing

Why: The first step is to make a connection between the program text and the behaviour you are seeing, which means having a claim specific enough to test. Without one, each run produces no information, which is what the book calls random walk programming.

14. Explain it yourself: why is speed the difficulty?

Explain it to yourself

The book names it explicitly.

Discussion prompt

One of the things that makes forming a hypothesis hard is that computers run so fast. Explain why that matters.

Hint: What would you like to watch?

Answer:

Because the behaviour you want to explain happens between two moments you can see: the input and the output. Everything in between is over before you could observe it.

So the hypothesis has to be about something invisible, which is much harder than describing something you watched. You are reasoning about a process rather than reporting one.

Which is what a print statement fixes — it makes one moment visible at human speed. That is also why a debugger is appealing and why the book still prefers prints: both solve the same problem, and one of them costs less to set up.

15. Simplify the output, or simplify the program

Section

Section 2

16. When there is too much to read

Concept

One of the problems with using print statements for debugging is that you can end up buried in output. There are two ways to proceed: simplify the output, or simplify the program.

And the observation that makes this more than housekeeping: often the process of finding the minimal test case leads you to the bug.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 197-198

17. Picture it: two directions of simplification

Picture it

Less output, or less program.

Figure (svg): Two columns comparing simplifying the output with simplifying the program

Two ways to proceed when you are buried in output.

The right-hand column has a side effect the left does not: the process of finding the minimal test case leads you to the bug surprisingly often.

18. Worked example: why minimising finds bugs

Worked example

The search is itself a diagnosis.

# 'Often the process of finding the minimal test case
#  leads you to the bug. If you find that a program works
#  in one situation but not in another, that gives you a
#  clue about what is going on.'
StageWhat it producesNote
a case that failsand one that worksthe difference
the differenceis the clueabout what is going on
minimisingisolates that differenceby construction

Note what minimising involves.

Why: Repeatedly removing something and asking whether the failure survives — which produces pairs of nearly identical cases, one working and one not.

Note what those pairs are.

Why: Each one isolates a single difference that turns a working program into a failing one.

Note the consequence.

Why: If you find that a program works in one situation but not in another, that gives you a clue about what is going on — so the search produces evidence as a by-product.

Figure (svg): A flowchart showing minimisation producing a working and a failing case that differ by one thing

Every time something has to go back, you have learned that it matters.

A technique whose process is as valuable as its result. That is unusual, and it is why the book recommends minimising even when the output is manageable.

Verify: Connect it to lesson 11c's advice.

Why: Reduce n to the smallest value that manifests the error, and then increase it gradually as you find and correct errors — the same technique for large datasets. Both are bisection, and both work because the boundary between working and failing is where the bug lives.

19. Predict: what does minimising give you?

Prediction

Beyond a smaller test case.

# you reduce a failing input until it is minimal
What you getWhy it helpsNote
the smaller caseeasier to readthe obvious benefit
the pairs along the waywork / failthe clue
the differencepoints at the bug

Predict first

What else does the process of minimising a test case produce?

  • Clues — a program that works in one situation and not another tells you what is going on
  • A faster program
  • Nothing beyond a smaller input
  • A guarantee that the bug is in the removed code

Correct: Clues — if you find that a program works in one situation but not in another, that gives you a clue about what is going on.

Why: Every step of minimising produces two nearly identical cases with different outcomes, and each such pair isolates one difference that matters. That is why the book says the process often leads you to the bug — the result is a by-product of the search rather than the point of it.

20. Worked example: rewriting as a diagnostic

Worked example

A change that should not matter, and does.

# 'Similarly, rewriting a piece of code can help you
#  find subtle bugs. If you make a change that you think
#  shouldn't affect the program, and it does, that can
#  tip you off.'
ObservationWhat it impliesNote
a neutral changeshould change nothingby your understanding
it changes somethingyour understanding is wrongsomewhere
the tip-offwhere to look

Make a change you believe is neutral.

Why: Reorganising, renaming, splitting a function — anything that should leave the behaviour identical.

Watch for a difference.

Why: If the behaviour changes, one of your beliefs about the code is wrong, and the change you made points at which.

Note why this works.

Why: It tests your model rather than the program, which is exactly what a semantic error requires.

Figure (svg): The state of the program after each line of Worked example rewriting as a diagnostic, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A change that is informative whether or not it helps. This is the same logic as the next idea's mental-model point, applied as a technique.

Verify: Ask what it means when nothing changes.

Why: That your understanding of that part was right — which is a small confirmation rather than nothing. Eliminating a correct belief is progress in a search where the difficulty is that you cannot tell which of your beliefs is wrong.

21. Trap: adding prints without removing any

Trap

The trap

Prints accumulate through a debugging session until the output is thousands of lines.

Keep everything, in case it is needed

Why: Removing one might mean adding it back.

You end up buried in output, in which the interesting line is invisible — so the technique stops working through sheer volume rather than through any fault in it.

The fix

Prune as you go.

Remove or comment out print statements that aren't helping

Why: Which is the book's first suggestion.

Or combine them, or format the output so it is easier to understand

Why: Which keeps the information and reduces the volume.

This is lesson 11c's problem in another form: a technique that works perfectly at small scale and fails at large one, where the fix is to reduce the scale rather than to abandon the technique.

22. Sort: simplify the output, or the program?

Sorting

Two directions, and the book distinguishes them.

Sort into buckets

For each action, which kind of simplification is it?

simplify the output
commenting out prints that aren't helping; formatting the output so it lines up; combining several prints into one
simplify the program
searching a small list instead of a large one; splitting a large function into smaller ones; removing dead code
out
Each reduces what you have to read without changing the program — the quick option when the volume is the obstacle.
prog
Each changes the program or its input to make it easier to understand — slower, and it often finds the bug as a side effect.

23. Complete it: scale down the problem

Faded example

The simplest input that still fails.

Fill in the blanks

# give it the simplest input that causes the problem

Why: Minimising makes the output readable and produces clues along the way — every time something has to stay for the failure to survive, you have learned that it matters. That is why the book says the process of finding the minimal test case often leads you to the bug.

24. Where narrowing down is the diagnosis

Real world

Isolating a fault by removing things.

Discussion prompt

Think of a fault someone found by disconnecting things one at a time. What did the process tell them beyond which part was broken?

Hint: What happened when they put something back?

Answer:

Which combinations matter. Removing one thing and finding the fault persists is different from removing it and finding the fault gone — and both are informative.

So the search produces a map as well as an answer: by the end, you know which parts are involved and which are irrelevant, which is more than the location alone.

Which is why the book says the process often leads you to the bug. The minimal case is the destination, and the pairs of working and failing versions along the way are the evidence.

25. Big hairy expressions

Section

Section 3

26. Break it into temporary variables

Concept

Writing complex expressions is fine as long as they are readable, but they can be hard to debug. It is often a good idea to break a complex expression into a series of assignments to temporary variables.

# one expression
self.hands[i].addCard(self.hands[self.findNeighbor(i)].popCard())

# three assignments
neighbor = self.findNeighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard(pickedCard)
VersionWhat you can checkNote
the one-linernothing to inspectno intermediate values
the three linestwo named intermediateseach printable
the namesadditional documentationneighbor, pickedCard

The explicit version is easier to read because the variable names provide additional documentation, and it is easier to debug because you can check the types of the intermediate variables and display their values.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 199-199

27. Picture it: two things the split buys

Picture it

Readability and inspectability, separately.

Figure (svg): Two columns comparing a nested expression with the same computation split into steps

Easier to read because of the names, and easier to debug because of the values.

Which is the same argument as lesson 19a's about comprehensions: a form with nowhere to put a print is a form you cannot inspect.

28. Worked example: the precedence bug

Worked example

An expression that means something other than it looks like.

# translating x / (2 pi) into Python:
y = x / 2 * math.pi

# multiplication and division have the same precedence
# and are evaluated from left to right,
# so this computes x pi / 2

y = x / (2 * math.pi)      # correct
ExpressionWhat it computesNote
/ and *the same precedenceleft to right
x / 2 * pi(x / 2) * pinot what was meant
parenthesesmake it explicitand correct

Note the rule.

Why: Multiplication and division have the same precedence and are evaluated from left to right.

Note what that produces.

Why: The expression computes x times pi over 2 rather than x over 2 pi — a wrong answer with no error.

Note the fix and its second benefit.

Why: A good way to debug expressions is to add parentheses to make the order of evaluation explicit. Not only will the program be correct, it will also be more readable for other people who haven't memorised the order of operations.

Figure (svg): A panel showing a precedence bug producing a wrong result with no error

A one-character-class difference that changes the meaning entirely. Whenever you are not sure of the order of evaluation, use parentheses.

Verify: Check the two forms on a value you can compute.

Why: With x = 2, the wrong version gives about 3.14 and the right one about 0.318 — an order of magnitude apart, which is the kind of discrepancy that gets noticed. A subtler precedence bug can differ by a small amount and survive casual checking, which is why parentheses are worth adding pre-emptively.

29. Predict: what does this compute?

Prediction

Division and multiplication, unparenthesised.

y = x / 2 * math.pi
StepWhat happensNote
/ and *same precedenceleft to right
firstx / 2
thentimes pi(x / 2) * pi

Predict first

What does this expression compute?

  • x times pi, divided by 2
  • x divided by 2 pi
  • x divided by 2, then divided by pi
  • It depends on the value of x

Correct: x times pi, divided by 2 — multiplication and division have the same precedence and are evaluated from left to right.

Why: The intended expression, x over 2 pi, needs parentheses: x / (2 * math.pi). This is a semantic error of the purest kind — every step is legal, the arithmetic is performed correctly, and the answer is wrong. A good way to debug expressions is to add parentheses to make the order of evaluation explicit.

30. Worked example: a return you cannot inspect

Worked example

The same problem, in the one place it is worst.

# no chance to see the value
return self.hands[i].removeMatches()

# now you can display it
count = self.hands[i].removeMatches()
return count
VersionWhat you can checkNote
returning directlythe value never has a namenothing to print
assigning firsta named valueprintable
the extra linecosts nothingat run time

Note the problem.

Why: If you have a return statement with a complex expression, you don't have a chance to print the result before returning.

Apply the same fix.

Why: Again, you can use a temporary variable.

Note what it enables.

Why: Now you have the opportunity to display the value of count before returning.

Figure (svg): The state of the program after each line of Worked example a return you cannot inspect, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

One extra line, and the returned value becomes inspectable. The pattern is the same as splitting any expression, applied where the value would otherwise leave the function unseen.

Verify: Ask whether to remove the temporary afterwards.

Why: Not necessarily — the variable name provides additional documentation, which is a benefit independent of debugging. count says what the returned number means, where the expression only says where it came from.

31. Trap: trusting a remembered precedence

Trap

The trap

An expression is written from memory of the operator precedence rules, without parentheses.

Rely on knowing the rules

Why: They are fixed and learnable.

A wrong recollection produces a wrong answer with no error at all — and division and multiplication sharing a precedence is exactly the case people misremember, because the written formula groups them differently.

The fix

Add parentheses when you are not sure.

Whenever you are not sure of the order of evaluation, use parentheses

Why: Which is the book's own rule.

And they help the reader too

Why: It will be more readable for other people who haven't memorised the order of operations.

The cost is two characters and the benefit is that the code says what it means rather than relying on a shared memory. That is the same reasoning as naming a temporary variable.

32. Complete it: make the order explicit

Faded example

The denominator is the whole product.

Fill in the blanks

y = x / **(2 * math.pi)**

Why: Without the parentheses, division and multiplication share a precedence and evaluate left to right, so x / 2 * math.pi computes x pi / 2. Whenever you are not sure of the order of evaluation, use parentheses — and they make the code readable for people who have not memorised the order of operations.

33. Compare: one expression and several assignments

Comparison

Fill the blanks. The computation is identical.

Comparison matrix

QuestionOne expressionTemporary variables
How many lines?oneone per intermediate step
Can you print the middle?no — the values have no namesyes — each is named
What do the names add?nothing; there are noneadditional documentation
Can you check the types?noyes, on each intermediate

The book gives both benefits: easier to read because of the names, and easier to debug because of the values.

34. Explain it: my formula gives the wrong number

Explain it

The arithmetic looks right.

Discussion prompt

A classmate translated a formula into Python and the result is wrong by a factor. Suggest what to check first.

Hint: How does the written formula group things?

Answer:

The order of evaluation. A written formula groups things visually — a denominator is everything under the line — and Python groups by precedence, which is not the same.

The classic case is division and multiplication, which share a precedence and evaluate left to right: x / 2 * pi computes x pi / 2 rather than x over 2 pi.

So the fix is parentheses around anything the written form groups, and the general rule is the book's: whenever you are not sure of the order of evaluation, use them. A factor-sized error is exactly the signature.

35. The problem may be in your model

Section

Section 4

36. Not in the program

Concept

In order to program, you need a mental model of how programs work. If you write a program that doesn't do what you expect, often the problem is not in the program; it's in your mental model.

# the fix:
#   break the program into its components
#   (usually the functions and methods)
#   and test each component independently

# once you find the discrepancy between your model
# and reality, you can solve the problem
PartWhat is trueNote
the programdoes what it saysalways
your expectationmay be wrongabout what it says
testing componentsfinds the discrepancy

That is a strong claim and a useful one: it redirects the search from the code to the beliefs about the code, which is where a persistent semantic error usually lives.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 199-199

37. Picture it: two places the mismatch can be

Picture it

The program and your understanding of it.

Figure (svg): Two columns locating a discrepancy either in the program or in the model of it

Often the problem is not in the program; it's in your mental model.

And the second is harder to find precisely because you are using the faulty model to search — which is why testing components independently works where re-reading does not.

38. Worked example: testing a belief

Worked example

Break it into components and check each.

# you believe: this function returns a sorted list
>>> t = [3, 1, 2]
>>> result = t.sort()
>>> result
None                    # the belief was wrong
StageWhat happensNote
the beliefsort returns the sorted listplausible
the testone line in the interpreterimmediate
the resultNonethe model is corrected

Name the belief.

Why: Something you assume about a function or a method — often about what it returns, or whether it modifies its argument.

Test it in isolation.

Why: Break the program into its components — usually the functions and methods — and test each independently.

Correct the model.

Why: Once you find the discrepancy between your model and reality, you can solve the problem.

Figure (svg): The state of the program after each line of Worked example testing a belief, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A belief tested in one line, and corrected. This is the sort-returns-None trap from lesson 10b, found by testing rather than by reading.

Verify: Ask why reading would not have found it.

Why: Because the line t.sort() looks exactly right if you believe sort returns a list — the code is consistent with the wrong model, so re-reading confirms it. Only running the piece in isolation puts your belief against reality, which is what the technique is for.

39. Predict: where is the problem?

Prediction

The code has been read carefully and looks right.

t = [3, 1, 2]
result = t.sort()
print(result[0])
# TypeError: 'NoneType' object is not subscriptable
AspectWhat is trueNote
the codereads as correctif sort returns a list
the beliefsort returns a listwrong
the realitysort returns None

Predict first

What is the problem here?

  • A belief about what sort returns — the mental model, not the code's structure
  • A syntax error in the assignment
  • The list is too short
  • sort cannot be called on a list

Correct: A belief about what sort returns — often the problem is not in the program; it's in your mental model.

Why: The code is perfectly consistent with the belief that sort returns the sorted list, which is why re-reading confirms it. Only running the piece in isolation — checking what t.sort() actually returns — puts the belief against reality. That is why the book recommends testing components rather than reading them.

40. Worked example: build and test as you go

Worked example

The advice that prevents the problem.

# 'Of course, you should be building and testing
#  components as you develop the program. If you
#  encounter a problem, there should be only a small
#  amount of new code that is not known to be correct.'
PracticeWhat it gives youNote
incremental developmenteach piece testedas it is written
when a problem appearsthe untested code is smalla bounded search
without iteverything is suspectan unbounded one

Note the practice.

Why: You should be building and testing components as you develop the program — chapter 4's incremental development, restated as a debugging strategy.

Note what it buys.

Why: If you encounter a problem, there should be only a small amount of new code that is not known to be correct.

Note the alternative.

Why: Writing everything and then testing means the bug could be anywhere, and every belief about every part is unverified.

Figure (svg): A flowchart contrasting a bounded search after incremental testing with an unbounded one

There should be only a small amount of new code that is not known to be correct.

A bounded search instead of an unbounded one. The same advice appears in lesson Aa: if you are building the program incrementally, the error will be in the last line you added.

Verify: Check it against the deliberate-error test's role.

Why: Both narrow the search before it begins — one by confirming you are running the right file, and this one by confirming most of the code is already known good. Debugging is much easier when the space of possible causes is small, and both techniques shrink it.

41. Trap: re-reading code that matches a faulty belief

Trap

The trap

A developer reads a section of code repeatedly, confirming each time that it is correct.

Check the code against your understanding

Why: Which is what reading is for.

If the understanding is the faulty part, every reading confirms it — the code is consistent with the wrong model, so reading cannot find the discrepancy. The confidence increases and the bug does not move.

The fix

Test the components against reality.

Run each function on a small input and check the result

Why: Which puts the belief against the behaviour.

Especially functions from other modules

Why: Where the book says to read the documentation and try them out with simple test cases.

The distinguishing symptom is confidence: reading that keeps confirming the code is correct, on a program that keeps being wrong, is reading against a faulty model. Testing is the only thing that can contradict it.

42. Two truths and a lie: mental models

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. If a program doesn't do what you expect, often the problem is in your mental model
  • B. The way to correct it is to test each component independently
  • C. Reading the code carefully enough will always reveal the discrepancy

Survives elimination: C

Why: C fails exactly when the model is at fault: the code is consistent with the faulty belief, so every reading confirms it. That is why the recommended technique is testing rather than reading — only running a component can contradict what you believe about it.

43. Complete it: correct the model

Faded example

Break it up and check each piece.

Fill in the blanks

# break the program into its components and
# test each one independently

Why: Testing a component on its own puts your belief about it against what it actually does, which is the only way to find a discrepancy in the model — reading cannot, because the code is consistent with the belief. And building and testing as you go means only a small amount of new code is ever unverified.

44. Think it through: why is a model error harder?

Socratic

The code is the same in both cases.

Discussion prompt

Explain why a faulty mental model is harder to find than a faulty line of code.

Hint: What are you using to search?

Answer:

Because you are searching with the faulty model. Every line you read is interpreted through it, so a line that means something other than you think will look correct every time.

A faulty line, by contrast, is wrong relative to a correct understanding — so reading with attention can find it, which is why that technique works at all.

Which is why the remedy is different in kind: not reading harder but testing, because a component run in isolation reports what it actually does rather than what you believe. That is the only source of information the model cannot contaminate.

45. Being stuck, and asking for help

Section

Section 5

46. The advice the book ends on

Concept

First, try getting away from the computer for a few minutes. The book lists the symptoms of staying too long — frustration and rage, superstitious beliefs and magical thinking, and random walk programming.

The joke about computers emitting waves that affect the brain is carrying real advice: frustration and magical thinking are recognisable states, and the recommended response to them is to stop rather than to continue.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 200-200

47. Picture it: the symptoms, and what they mean

Picture it

Three states that indicate it is time to stop.

Figure (svg): Three symptoms of being stuck too long with what each indicates

Each is recognisable from inside, which is what makes the list useful.

And the third is the one to catch earliest: writing every possible program and choosing the one that does the right thing is a state you can be in for a long time without noticing.

48. Worked example: preparing before you ask

Worked example

The book is specific about what to do first.

# before you bring someone else in:
#   the program should be as simple as possible
#   you should be working on the smallest input
#     that causes the error
#   you should have print statements in the
#     appropriate places, producing comprehensible output
#   you should understand the problem well enough
#     to describe it concisely
PreparationWhyNote
simplify firstthe minimal casewhich may find it
prints in placeand readableso evidence exists
a concise descriptionthe hardest partand often revealing

Simplify the program and the input.

Why: As simple as possible, and the smallest input that causes the error — which is the minimising from earlier, and it may resolve the problem before you ask.

Have evidence ready.

Why: Print statements in the appropriate places, and the output they produce should be comprehensible.

Be able to describe it concisely.

Why: Which requires understanding the problem well enough to state it — and stating it is the rubberducking that sometimes answers the question.

Figure (svg): Two columns comparing an unprepared request for help with a prepared one

Each preparation is useful in itself, which is why doing them often removes the need to ask.

Four preparations, each of which is useful even if nobody is asked. That is the point: doing them often removes the need.

Verify: Note the overlap with rubberducking.

Why: Understanding the problem well enough to describe it concisely is lesson 13c's technique — if you explain the problem to someone else, you sometimes find the answer before you finish asking the question. Preparing to ask and rubberducking are the same activity, which is why the preparation so often makes the asking unnecessary.

49. Sort: prepared, or not?

Sorting

Before bringing someone in.

Sort into buckets

For each, is it part of the book's preparation?

part of the preparation
reducing to the smallest input that fails; having prints in the appropriate places; being able to describe the problem concisely; knowing what you have tried and learned
not
showing them the whole untouched program; asking as soon as anything goes wrong
yes
Each is on the book's list, and each is useful whether or not you end up asking — which is why doing them often makes the asking unnecessary.
no
Neither prepares anything: one hands over the unsimplified problem, and the other skips the preparation entirely.

50. Worked example: what to tell them

Worked example

Three specific things.

# 1. If there is an error message, what is it and
#    what part of the program does it indicate?
# 2. What was the last thing you did before this
#    error occurred? The last lines you wrote, or
#    the new test case that fails?
# 3. What have you tried so far, and what have
#    you learned?
ItemWhat it gives themNote
the messageand wherethe evidence
what changed lastbounds the searchthe most useful question
what you triedavoids repetitionand shares the eliminations

Give them the evidence.

Why: The error message, if there is one, and what part of the program it indicates.

Give them the boundary.

Why: What was the last thing you did before this error occurred — which is the same question lesson 13c listed under ruminating.

Give them your eliminations.

Why: What have you tried so far, and what have you learned — so they do not repeat work you have already done.

Figure (svg): The state of the program after each line of Worked example what to tell them, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Three items that turn a request into a briefing. The third is the one people skip, and it is what stops the helper starting from nothing.

Verify: Note the closing instruction.

Why: When you find the bug, take a second to think about what you could have done to find it faster — next time you see something similar, you will be able to find it more quickly. That converts one bug into a technique, which is why the appendix ends with it.

51. Trap: continuing while frustrated

Trap

The trap

An hour into a bug, with rising frustration, the developer keeps changing things and running the program.

Keep working until it is fixed

Why: Stopping feels like giving up.

The book lists exactly this state and its symptoms: frustration and rage, superstitious beliefs, and random walk programming — the attempt to program by writing every possible program and choosing the one that does the right thing. None of them finds bugs.

The fix

Get up and go for a walk.

Then think about it away from the machine

Why: What is it doing, what could cause that, and when did it last work?

And accept that it sometimes takes time

Why: Some of the best places to find bugs are trains, showers, and in bed.

The advice is practical rather than sentimental: the recognisable symptoms mean you have stopped reasoning, and continuing in that state produces changes rather than progress.

52. Predict: what does the book recommend when stuck?

Prediction

After frustration has set in.

# symptoms: frustration and rage, superstitious
# beliefs, random walk programming
StageWhat to doNote
the symptomsrecognisable from insidewhich is the point
the recommendationget up and go for a walk
thenthink about it calmlyaway from the machine

Predict first

What does the book recommend?

  • Get away from the computer for a few minutes, and think about it when calm
  • Keep trying changes until something works
  • Rewrite the program from scratch immediately
  • Ask for help before doing anything else

Correct: Get away from the computer for a few minutes, and think about it when you are calm.

Why: The symptoms are listed because they are recognisable from inside: frustration and rage, superstitious beliefs, and random walk programming. None of them finds bugs, and the book notes that some of the best places to find bugs are trains, showers, and in bed, just before you fall asleep.

53. Complete it: the closing instruction

Faded example

After the bug is found.

Fill in the blanks

# take a second to think about what you could
# have done to find it faster

Why: Next time you see something similar, you will be able to find the bug more quickly — which converts one debugging session into a technique. That is why the appendix ends with it, and it leads directly into the book's last line: the goal is not just to make the program work, but to learn how to make the program work.

54. Where stepping away is the technique

Real world

A problem solved by not working on it.

Discussion prompt

Think of something you solved after stopping — the answer arriving on a walk, in the shower, or the next morning. What had changed?

Hint: Not the problem.

Answer:

Not the problem, and not what you knew about it. What changed is that you stopped following the same path through it, which is what fixation does — the same wrong assumption gets re-applied every time.

The book names the places: trains, showers, and in bed, just before you fall asleep. They have in common that you cannot act, so the only available move is to think differently.

Which makes stepping away a technique rather than a rest. The symptoms it treats — frustration, magical thinking, random walk programming — are all states in which more effort produces less.

55. Compare: what each error kind requires

Comparison

Fill the blanks. The techniques do not transfer.

Comparison matrix

QuestionSyntax and runtimeSemantic
What does the interpreter give you?a message and a locationnothing at all
What is the first move?read the messageform a hypothesis about what it is doing
What is the main tool?the message and the tracebackwell-placed print statements
Where might the fault be?in the programin the program, or in your mental model

The bottom row is the difference that matters most: only one kind of error can be caused by believing something untrue about correct code.

56. The procedure: a semantic error

Pattern

Six steps, and the fifth is the one people skip.

  1. Ask the three questions: is something not happening, is something happening that should not, or is an effect not what you expected?
  2. Scale down to the smallest input that causes the problem — and watch for clues as you do.
  3. Break complex expressions into temporary variables so the intermediate values can be printed.
  4. Test components independently, especially functions from other modules, in case the fault is in your model.
  5. If frustration, magical thinking or aimless changes appear, get away from the computer.
  6. When you find it, think about what would have found it faster.

Step 5 is not a concession. The symptoms the book lists are states in which further effort produces changes rather than progress, and recognising them is a skill.

Python documentation — Errors and Exceptions Errors and Exceptions

57. Check yourself 1 of 3: what makes them hardest

Check

Compared with the other two kinds.

Check your understanding

Why are semantic errors the hardest to debug?

  • A. The interpreter provides no information, because only you know what the program is supposed to do (correct)
  • B. They occur in more complicated programs
  • C. They cannot be reproduced
  • D. They always involve recursion

Answer: A

Why: Every step of the program was legal, so nothing is reported — and what it should have done is information the program does not contain. That is why the first step is to form a hypothesis rather than to read a message.

Why B tempts people
A one-line expression can have one, as the precedence example shows.
Why C tempts people
They are usually perfectly reproducible; the program does the same wrong thing every run.
Why D tempts people
Recursion is unrelated. Infinite recursion is a runtime error.

58. Check yourself 2 of 3: precedence

Check

Two operators of equal precedence.

y = x / 2 * math.pi
PartWhat appliesNote
/ and *the same precedence
evaluationleft to right
the result(x / 2) * pi

Check your understanding

What does this compute?

  • A. x times pi, over 2 (correct)
  • B. x over 2 pi
  • C. x over 2, then over pi
  • D. It is a syntax error

Answer: A

Why: Multiplication and division have the same precedence and are evaluated from left to right, so the division happens first. Writing x / (2 * math.pi) makes the intended grouping explicit — and whenever you are not sure of the order of evaluation, use parentheses.

Why B tempts people
That is what was intended, and it needs the parentheses to happen.
Why C tempts people
That would be x / 2 / math.pi, which is a different expression.
Why D tempts people
It is perfectly legal, which is exactly what makes it a semantic error.

59. Check yourself 3 of 3: the model

Check

The code has been read many times.

Check your understanding

You keep reading a section of code and it keeps looking correct, but the program is wrong. What does the book suggest?

  • A. The problem may be in your mental model — test the components independently (correct)
  • B. Read it once more, more slowly
  • C. The error must be elsewhere in the file
  • D. Rewrite it in a different style

Answer: A

Why: If you write a program that doesn't do what you expect, often the problem is not in the program; it's in your mental model — and reading cannot find that, because the code is consistent with the faulty belief. Testing each component independently puts the belief against reality.

Why B tempts people
More of what has already failed. Every reading interprets the code through the same model.
Why C tempts people
It might be, and the repeated confirmation is itself a signal worth acting on first.
Why D tempts people
Rewriting is a genuine technique — a change that unexpectedly matters is a tip-off — but the direct answer is to test the components.

60. Where this shows up outside this course

Real world

Something that works exactly as built and not as intended.

Discussion prompt

Think of a set of instructions followed precisely with the wrong outcome. Where was the fault?

Hint: Not in the following.

Answer:

In the instructions, or in what whoever wrote them believed about the situation — never in the following, which was exact.

Which is the shape of every semantic error: the program did what it says, and what it says is not what was meant. Nothing in the execution can report that, because the execution was correct.

And the harder version is the book's point about the mental model: when the instructions match a wrong belief, re-reading them confirms them. Only testing against reality can find the discrepancy.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence.

Predict first

You have read a section of code many times and it looks correct every time, but the program still produces the wrong answer. What does the book suggest?

  • The problem may be in your mental model — break the program into components and test each independently
  • Read it again with fresh attention
  • The error is definitely somewhere else in the program
  • Add more print statements to the same section

Correct: The problem may be in your mental model — break the program into components and test each independently.

Why: This is the appendix's strongest claim about semantic errors, and it explains a frustrating experience precisely: in order to program, you need a mental model of how programs work, and if you write a program that doesn't do what you expect, often the problem is not in the program, it's in your mental model. The reason re-reading fails is structural rather than a matter of attention — you are reading through the faulty model, so code that means something other than you believe will look correct every single time. A line like result = t.sort() is perfectly consistent with the belief that sort returns the sorted list, and no amount of careful reading contradicts a belief the code agrees with. The only source of information the model cannot contaminate is running a component in isolation and seeing what it actually does. That is why the recommended technique is testing rather than reading, and why the book adds that you should be building and testing components as you develop the program — so that when a problem appears, there is only a small amount of new code that is not known to be correct.

62. Explain it to someone else

Explain it

A program that runs perfectly and does the wrong thing.

Discussion prompt

A classmate's program produces the wrong answer with no error message and they do not know where to start. Give them the first three moves.

Hint: There is no message, so something has to replace it.

Answer:

Form a hypothesis. Ask which of the three shapes it is: something that should be happening is not, something is happening that should not, or an effect is not what they expected. Each names a section of code and something checkable about it.

Scale it down. Give the program the simplest input that causes the problem — and watch for clues, because a case that works next to one that fails tells you what the difference is.

Then test the components rather than reading them. If the code keeps looking correct, the fault may be in what they believe about it, and only running a piece in isolation can contradict that.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • Forming a hypothesis, and the three diagnostic questions
  • Simplifying, and why minimising finds bugs
  • Temporary variables, and the precedence trap
  • The mental model, and knowing when to stop or ask

Correct: Whichever you picked is the right answer — this one is for you, not for a mark.

Why: The three questions are what replace the error message you do not have, and each converts a complaint into something testable. Minimising has the unusual property that the process finds bugs as often as the result does. The precedence case is the purest semantic error in the book — legal, arithmetically correct, and wrong — and the fix is two characters. And the mental-model claim is the one worth carrying furthest, because it explains why re-reading correct-looking code can fail indefinitely.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Write the three diagnostic questions and, beside each, what it directs you to check. Underneath, write the nested expression and its split-into-temporaries version, marking what each intermediate name buys. Then write x / 2 * math.pi and what it actually computes. Finally write in one sentence why re-reading cannot find a faulty mental model, and list the three symptoms that mean it is time to step away.

65. What you can do now

Recap

Three pages, and the appendix is finished: the error kind with no message.

If you remember one thingIt is this
From semantic errorsOnly you know what the program is supposed to do.
From minimisingThe search produces clues, not just a smaller case.
From expressionsA value with no name is a value you cannot inspect.
From precedenceDivision and multiplication are equal, and go left to right.
From the mental modelReading confirms a faulty belief; only testing contradicts it.

The last two lessons cover appendix B: what it means for one algorithm to be faster than another, the order of growth of the operations you have been using all along, and how a hashtable makes dictionary lookup take about the same time however large the dictionary gets.

Think Python, 2nd edition — Allen B. Downey §A.3, pp. 198-200 — everything on these slides traces back here

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §A.3, pp. 198-200
  2. Python documentation — Errors and Exceptions
  3. Python documentation — Built-in Functions

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108