Lesson 10.1Lesson 10.1 · Practice, AI-Assisted Coding & Career
Debugging and Good Code
Every coder writes bugs; the skill is reading the error, cornering the cause, and leaving code your future self can still read
The error message is not the computer scolding you. It is the computer telling you exactly where it got stuck - if you slow down and read it.
Every programmer who has ever lived writes bugs, constantly, all day. The difference between someone who codes fluently and someone who gives up is not that the fluent one avoids errors - it is that they have stopped being frightened of them and learned to read them. A traceback is not a punishment; it is a map.
This lesson is about the two most under-taught skills in coding: finding out why something broke, calmly and methodically, and writing code that will not break your own brain when you reopen it in three months. Neither is glamorous. Both are what separate a script you can trust from one you dread touching.
Read the last line first. One change at a time. Good names beat clever tricks.
Read the error - it is telling you where and what
When a script fails, Python prints a traceback, and the single biggest beginner mistake is to look away from it in a panic. Do the opposite: read it, and read it from the bottom up. The very last line is the actual error - its type (like NameError, TypeError, IndexError) and a short message. The lines above are the trail of function calls that led there, each naming a file and line number. That line number is a gift: it tells you exactly where to look.
Consider a tiny schedule script with a typo:
width = 3.6
area = width * heigth
print(area)Run it and Python says:
NameError: name 'heigth' is not definedThat one line solves the whole thing. NameError means you used a name Python has never seen - almost always a typo or a variable you forgot to create. Here heigth should be height. You did not need to understand the program; you needed to read the message. Learn to recognise the common types on sight: NameError (unknown name, usually a typo), TypeError (wrong kind of value, like adding a number to text), IndexError (asked for item 10 of a 3-item list), KeyError (a dictionary key that is not there), IndentationError and SyntaxError (the code is not shaped like valid Python), and FileNotFoundError (the path is wrong). Nine times in ten, the type plus the line number is the whole answer.
Read the traceback from the BOTTOM up. Last line = the real error.
Corner the bug - print, then the debugger
Not every bug announces itself with a traceback. The nastier kind runs to the end and gives the wrong answer - a schedule totals that are off, a loop that does one item too few. For these you need to see what the code is actually doing, not what you assume it is doing. The oldest, most reliable tool is the humble print(). Sprinkle it to reveal the values at each step:
rooms = [("living", 24.0), ("kitchen", 12.5), ("bath", 0)]
total = 0
for name, area in rooms:
print(f"adding {name}: {area}") # what am I really seeing?
total += area
print(f"total = {total}")Those print lines turn an invisible process into a visible one. You will often spot the bug the instant you see a value you did not expect - a 0 where there should be a number, a string where you assumed a float. This is called print debugging, and no one is too advanced for it.
When prints get unwieldy, step up to a debugger - built into VS Code and every serious editor. You set a breakpoint on a line, run, and execution pauses there so you can inspect every variable and step forward one line at a time. It is print debugging with a control panel. The crucial mindset for both: change only one thing at a time, then re-run. If you change three things at once and the bug goes away, you have learned nothing about which change mattered - and often introduced a new bug. Observe, form one guess, test that one guess, change one thing. That loop, repeated, corners any bug.
The bugs every beginner hits
A surprising share of beginner bugs are the same half-dozen mistakes, and knowing them by name saves hours. Off-by-one and range confusion: range(1, 5) gives 1, 2, 3, 4 - it stops before the second number, so it is four items, not five. Indentation: Python uses spaces to mean 'this belongs inside the loop/if'; mixing tabs and spaces, or getting the level wrong, changes what your code does or breaks it outright. The `=` versus `==` slip: a single = assigns a value, double == compares - if area = 15 is an error; if area == 15 is a test. Integer versus string: "3" + "6" is "36", not 9, because they are text; you must convert with int() or float() first. Mutating a list while looping over it, and the classic default-list trap, come later. And the one that catches everyone: comparing floats for exact equality, when 0.1 + 0.2 is not quite 0.3 in binary.
Here is the string-versus-number trap made concrete, because it appears constantly when reading data from files:
value = "3.6" # came from a CSV, so it is TEXT
area = value * 2 # "3.63.6" -- probably not what you wanted
area = float(value) * 2 # 7.2 -- convert firstNone of these are signs you are bad at coding. They are the potholes on a road everyone walks. Once you have hit each a few times, you start to smell them coming - and that instinct is most of what 'getting good at debugging' actually means. A practical way to speed that up is to keep a personal note of the bugs that cost you time and how you fixed them; within a few weeks you will notice the same handful recurring, and the note becomes a checklist you run almost automatically. The professionals who seem to never get stuck are not smarter - they have simply hit these potholes so many times that recognising them is instant.
range(1,5) = 1,2,3,4. '=' assigns, '==' compares. '3'+'6' = '36', not 9.
Good code is code your future self can read
The other half of this lesson is writing code that does not become a bug in itself. Your scripts can be rough and personal, but 'rough' should mean short and honest, not unreadable. The single highest-value habit is good names. Compare:
x = a * b
for i in d:
r.append(i[0] * i[1])with:
area = width * height
for room in rooms:
areas.append(room.width * room.height)Both run identically. Only one you can still understand next month. Names are free documentation; spend them generously. Next, keep functions small and single-purpose - a function called export_schedule() should export a schedule, not also rename files and send an email. If you cannot describe what a function does in one sentence, it is doing too much. Third, write comments that explain _why_, not _what_ - the code already says what; a good comment says the reason: # skip rooms under 2 sqm - they are cupboards, not habitable. Comments that just narrate the obvious (# add one to i) are noise.
Finally, prefer clear over clever. A three-line version anyone can read beats a one-line trick only you can decode, especially when you are the one debugging it at 6pm before an issue. When you do get truly stuck - and you will - the way out is method, not force: read the error aloud, reduce the problem to the smallest script that still fails, explain it line by line to a colleague or a rubber duck (saying it aloud exposes the flaw astonishingly often), search the exact error message, and only then reach for an AI assistant (Lesson 10.3). Getting unstuck is a skill, and it is mostly patience plus a system.
Good names + small functions + why-comments = readable next month.
Prevent bugs by running early and often
The cheapest bug to fix is the one you catch a second after you make it, while you still remember exactly what you changed. That single fact should reshape how you work. Beginners tend to write forty lines and then run for the first time, and when it fails they face a wall of possible causes. Fluent coders do the opposite: they write two or three lines, run, check the result, and only then continue. Errors caught this way are almost trivial, because there is only one recent change that could have caused them.
Build the habit of proving each piece before you build on it. Print an intermediate value; check that a list has the length you expect; confirm a file opened before you loop over it. In a notebook (Jupyter, from Module 0) this is natural - you run a cell, see the output, and move on. In a plain script, add a temporary print, run, then delete it. Either way you are converting one big terrifying test at the end into many tiny reassuring ones along the way.
A second prevention habit is to handle the messy input on purpose rather than be surprised by it. Real design data is dirty - blank cells, text where you expected numbers, a stray header row - so write your code expecting that. A defensive read looks like this:
def to_area(value):
try:
return float(value)
except (ValueError, TypeError):
return 0.0 # blank or text -> treat as zero, and log itWrapping the risky conversion in try/except means one bad cell no longer crashes the whole run. You do not need this everywhere - throwaway scripts can stay bare - but for anything you will run more than once, anticipating the mess is far cheaper than debugging the crash. Prevention is not a separate discipline from debugging; it is debugging moved earlier, where it is easy.
Cheapest bug = caught one second after you make it. Run every few lines.
Traceback
Python's report of where and why code failed
Read it bottom-up: the last line is the real error type and message; lines above are the call trail with file and line numbers.
Exception types
NameError, TypeError, IndexError, KeyError, ...
Each names a category of mistake. Learning the common six on sight solves most beginner bugs instantly.
print() debugging
Revealing hidden values by printing them
The simplest, most universal debugging tool. No one is too advanced for a well-placed print().
Debugger / breakpoint
Pausing execution to inspect variables
Built into VS Code. A breakpoint halts on a line so you can step forward and watch values change - print debugging with a control panel.
Workshop - fix a broken schedule script
The fastest way to learn debugging is to debug. Below is a short script that is supposed to total the areas of habitable rooms, but it has three bugs planted in it. Your job is to find and fix all three using only the traceback and print().
Python 3 and a text editor or VS Code. No libraries required.
Goal: fix three bugs so the script prints the correct habitable total Inputs: the buggy snippet below, Python installed Time: ~25 minutes
- 1Type this into a file and run it:
rooms = [("living", "24"), ("kitchen", "12.5"), ("store", "1.5")]then a loopfor name, area in rooms:that doesif area > 2: total = total + area, and finallyprint(total). Run it and read the FIRST traceback carefully. - 2Bug one is a NameError:
totalis used before it exists. Read the error, then fix it by creatingtotal = 0before the loop. Re-run and read the NEXT error - change only that one thing first. - 3Bug two is a TypeError: the areas are strings from a would-be CSV, so
area > 2compares text to a number. Addarea = float(area)at the top of the loop. Insert aprint(name, area)line to confirm the values are now numbers. - 4Bug three is logic, not a crash: the store at 1.5 sqm should be excluded as non-habitable, and it is - but check the boundary. Should a room of exactly 2.0 sqm count? Decide, then make the comparison say what you mean (
>vs>=). Print the running total to watch it build. - 5Once it prints the right number, do a readability pass: rename
totaltohabitable_total, add a one-line comment saying WHY 2 sqm is the cutoff, and remove your debug prints.
You’ll walk away with
A working script that prints the correct habitable-area total, plus a two-sentence note naming each of the three bugs (NameError, TypeError, and the boundary decision) and how you found it.
Three altitudes on the same idea
Read the band that fits you — or all three.
When a script drives issued drawings or a schedule, a silent bug is a professional risk, not just an annoyance. The habits here - reading the traceback, changing one thing at a time, testing on a small sample before running on the whole project - are exactly the discipline that keeps an automation trustworthy. Readable names and small functions also mean the graduate who inherits your script next year can maintain it instead of rewriting it.
Most of your scripts touch data - FF&E lists, finishes, quantities - where the wrong answer looks perfectly plausible. That makes print-debugging your best friend: print the totals and a few rows, eyeball them against the real schedule, and catch the off-by-one or the text-versus-number slip before it reaches a client. Clear variable names (fabric_cost, not x) turn a script you wrote once into one you can reuse across every project.
Debugging fluency is what employers actually watch for - not whether you can write perfect code first time, but whether you can fix it calmly when it breaks. Practise reading tracebacks now, on tiny programs, until the common error types are old friends. The same methodical loop scales straight into the Computational Design and BIM courses in this Academy, where the scripts get longer and the bugs more interesting.
“Good programmers do not get errors - if my code keeps breaking, I am not cut out for this.”
Do it yourself
Reason these through - most need no computer.
- 1In a traceback, which line do you read first, and what two things does it tell you?
- 2What does a NameError almost always mean in practice?
- 3Why should you change only one thing at a time when debugging?
- 4What is wrong with
"3" + "6"if you wanted 9, and how do you fix it? - 5Rewrite
x = a * bwith names that would make sense in a room-area script.
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Debugging — Wikipedia, 2026.
- 02The Python Tutorial — Python Software Foundation, 2026.
- 03Software documentation — Wikipedia, 2026.
- 04Integrated development environment — Wikipedia, 2026.
Debugging keeps a single script honest. But scripts grow, break in new ways, and sometimes you wish you could go back to yesterday's working version. Next we meet the tool that makes that possible - version control with git.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →