Debugging: find the bug, don't guess
The ship stops shooting
A report lands in #bug-reports: "After I use the pause menu, my ship can't shoot anymore."
Kai's first idea is to guess: change the fire key, then the laser speed, then ask Claude to "fix shooting". Three guesses later the bug is still there, and two new ones have arrived.
Debugging means finding the real cause of a bug, step by step, before changing anything. Professionals follow a loop: reproduce the bug, read the clues, narrow down where it lives, then fix it and prove the fix works.
First, make it happen on purpose
A bug you can make happen every time is half solved. Making it happen on purpose is called reproducing it. Kai writes the exact steps to reproduce:
- Start wave 1 and shoot: lasers work.
- Press P to pause, then P again to unpause.
- Press Space: no lasers.
Then two more lines. Expected: lasers fire. Actual: nothing happens, and no error appears. Kai also tries the edges. Does clicking Resume do the same? Does it happen in every browser?
Bolt says: Ask "what changed?" as well. The bug showed up the same week as the new pause menu, which is a big clue.
Read the stack trace
Many bugs leave an error in the browser's console, the panel F12 opens (the prompts lesson shows how to paste one into a prompt). Under the error comes a stack trace: the chain of function calls that led to the crash.
TypeError: Cannot read properties of undefined at damagePest (pests.js:42:20) at checkHits (garden.js:88:7) at tick (main.js:15:3)
The first line says what went wrong. Each at line names a function, a file, a line and a column. The top one crashed. Each line below called the one above it.
Bolt says: The crash and the cause can be in different places. If damagePest got undefined instead of a pest, look at checkHits too, because it passed that in.
Read the trace
1TypeError: Cannot read properties of null2 (reading 'play')3 at playHitSound (sound.js:12:9)4 at onHit (ship.js:57:3)5 at update (main.js:30:5)
Kai reads this trace from Hyper Shards. The crash happened on line 57 of ship.js.
- True
- False
Show the answer
False
False. The top at line is where it crashed: playHitSound, line 12 of sound.js. Line 57 of ship.js is where onHit called playHitSound, which is the next place to look, because that call may have passed in the null.
Breakpoints: freeze the game
The pause bug prints no error, so Kai looks inside the running game. In the developer tools, open the Sources panel (Chrome and Edge) or the Debugger panel (Firefox), open ship.js and click a line number inside fireLaser. That marker is a breakpoint: the game freezes just before that line runs.
While frozen, Scope shows every variable's value right now. Watch follows values you choose, like input.enabled. Call Stack shows the calls that led here. Writing debugger; in the code also pauses there, while the tools are open.
One step at a time
Frozen at a breakpoint, these buttons move the game forward while you watch the values change:
- Step overRuns the current line, including any function it calls, and stops on the next line.
- Step intoIf the line calls a function, jumps inside it and stops on its first line.
- Step outFinishes the current function and stops where it was called from.
- ResumeLets the game run on until it reaches the next breakpoint.
- console.logA quick print without pausing, like console.log("fire", input.enabled). Handy, but you must guess what to print, and remove it afterwards.
Bolt says: Kai's breakpoint in fireLaser never pauses after the pause menu. That is a clue: fireLaser is never called, so the problem is earlier.
Bug hunt
1function openPause() {2 game.paused = true;3 input.enabled = false;4 showMenu('pause');5}6 7function closePause() {8 game.paused = false;9 hideMenu('pause');10}
Kai steps through closePause. Afterwards, Watch shows game.paused: false and input.enabled: false. Which fix makes the ship shoot again?
- Add input.enabled = true; inside closePause
- Delete line 2, so the game never pauses
- Change line 8 to game.paused = true
Show the answer
Add input.enabled = true; inside closePause
Line 3 turns input off when the menu opens, but closePause never turns it back on, so the fire key is ignored for ever. Deleting line 2 breaks pausing, and line 8 would keep the game paused.
Cut the search in half
No clue where a bug lives? Use a binary search: test the middle, throw away the half that works, repeat. Comment out half of a suspect function (turn lines into comments so they don't run). Bug gone? It's in that half. 1,000 lines take about 10 tests.
Git searches history the same way. A commit is a save point (next lesson). Run git bisect start, mark now as git bisect bad and an older working commit as git bisect good a1b2c3d. Git checks out the middle commit, you play and answer good or bad, until it names the first bad commit. 100 commits take about 7 tests.
Bolt says: Stuck? Explain the code out loud, line by line, to a rubber duck. Saying what each line should do often reveals the one that doesn't. This rubber duck debugging is named after a story in the 1999 book The Pragmatic Programmer.
True story: the first actual bug
In 1947, engineers were working on the Mark II, an early computer at Harvard University. Inside it, they found a moth stuck in one of its parts.
They taped the moth into their logbook and wrote next to it: "first actual case of bug being found." Grace Hopper, a mathematician and programmer who later became a US Navy rear admiral, was on the Mark II team, and the team helped make "bug" and "debug" everyday words for programmers. The page, moth and all, is now in the Smithsonian's National Museum of American History.
Every bug has a real cause somewhere. Narrowing down is how you find it.
Source: Smithsonian National Museum of American History: log book with computer bug, americanhistory.si.edu
Prompt pick
Kai found the cause and asks Generative AI Model Alpha to help fix it. Which message is best?
- "Shooting is broken after pause, fix it."
- "Steps: pause with P, unpause, press Space. Expected: lasers. Actual: nothing, no error. closePause in pause.js never re-enables input. Write a failing test, then fix."
- "Rewrite the pause menu and the input code from scratch, and make them better."
Show the answer
"Steps: pause with P, unpause, press Space. Expected: lasers. Actual: nothing, no error. closePause in pause.js never re-enables input. Write a failing test, then fix."
It has what a good bug report needs: steps to reproduce, expected, actual, and what Kai already found. Asking for a failing test first proves the bug exists, then proves the fix.
Prove it, then tidy up
Before fixing, write a test, a small program that checks the code gives the right answer. Ours pauses, unpauses and expects input.enabled to be true. It fails now. After the one-line fix, it passes. That is a regression test: it stops this bug from quietly coming back (the tests lesson shows how to write one).
Then tidy up. Delete every console.log you added while hunting. One inside the game loop prints 60 times a second, buries real errors, slows the game and can print private data. Read the diff before you save.
Bolt says: Reproduce, read, narrow down, fix, prove. Next lesson: Git commits, the save points that keep every fix safe.
Fact-checked 11 October 2026
- Chrome DevTools: debug JavaScript developer.chrome.com
- Chrome DevTools: JavaScript debugging reference developer.chrome.com
- Firefox DevTools: the debugger firefox-source-docs.mozilla.org
- MDN: debugger statement developer.mozilla.org
- Git: git bisect git-scm.com
- Wikipedia: rubber duck debugging en.wikipedia.org
- Smithsonian National Museum of American History: log book with computer bug americanhistory.si.edu