Webbo3 Level 2 · JavaScript Development · Day 14 of 20
Debugging, Testing and Performance
A developer day. You are given broken code and you have to find out why, which is most of what the job actually is.
By the end of today you can
- Read a stack trace instead of skipping past it
- Use breakpoints to stop time and look at your own variables
- Find the request that failed in the Network tab
- Write test cases for your own code, and know what to test
- Spot the three performance mistakes beginners actually make
Nobody writes code that works first time. Professionals are not people who avoid bugs; they are people who find them quickly.
Finding bugs is a learnable skill with real technique, and almost nobody is taught it. Most beginners debug by changing lines at random until the error goes away. That works occasionally and teaches nothing. Today you learn the technique.
1. Read the Error. All of It.
Definition
An error message is JavaScript telling you three things: what kind of problem happened (the type), what exactly went wrong (the message), and the chain of function calls that led there (the stack trace).
Real-Life Example
A mechanic's report does not just say "car broken". It says "brake fault, front left pad worn through, noticed after the Ikorodu trip". Kind of fault, exact part, and how you got there. An error message is that report, and beginners throw it away unread.
Types: The Errors You Will Meet
| Error | Almost always means |
|---|---|
ReferenceError: x is not defined | A typo, or the name was declared inside a block |
TypeError: Cannot read properties of undefined | Something before the dot does not exist yet |
TypeError: x is not a function | A typo in the method name, or x is not what you think it is |
SyntaxError: Unexpected token | A missing bracket or comma, usually just above the reported line |
Uncaught (in promise) | An async function with no catch |
How to Read One
- The type tells you the family. TypeError means you did something to a value its type does not support. ReferenceError means the name does not exist at all.
- The message names the property you were reaching for. The thing before the dot is the thing that is undefined.
- The stack reads top to bottom. The top line is where it broke; each line below it is the function that called the one above. Each line ends with (file:line:column).
Read it in order. TypeError: we used a value the wrong way. reading 'city': so the thing before .city is undefined, which is list[0].contact. at firstCity (app.js:6:26): it broke on line 6, column 26, and the next line down says line 9 called it. You now know the file, the line and the value without guessing.
The next step is to look at the value itself. Log the thing before the dot:
The student has a name and no contact. The code was right; the assumption about the data was wrong. The fix is to check before reaching:
Two more errors you will see weekly. First, a typo in a name:
Break your Day 9 project on purpose three ways: misspell a variable, misspell a method, and reach for a property that does not exist. Read each error aloud: type, message, file and line. Then fix each one using only what the error told you.
Picture the mechanic's report: kind of fault, exact part, and the trip that caused it. Read all three lines before you pick up a spanner.
2. Breakpoints Beat console.log
Definition
A breakpoint is a line where you tell the browser to pause your program before running it. While it is paused you can see every variable in reach, then move forward one line at a time.
Real-Life Example
A football replay. console.log is a single photo of the match: one moment, one angle. A breakpoint is pausing the replay: you see every player where they stand, then step forward frame by frame to watch the foul happen.
Types: How to Pause
| Kind | How | Use it when |
|---|---|---|
| Line breakpoint | DevTools, Sources tab, click a line number | You know roughly where it goes wrong |
| Conditional breakpoint | Right-click a line number, add a condition such as id === 47 | One bad row out of two hundred |
debugger statement | Write it in your code | You cannot find the file in Sources |
Moving While Paused
| Key | Does |
|---|---|
| F10 | Step over: run the next line and pause again |
| F11 | Step into: go inside the function on this line |
| F8 | Resume: run on to the next breakpoint |
While paused, hover any variable to see its value, and read the Scope panel for everything in reach.
The debugger statement pauses only when DevTools is open. With DevTools closed the browser ignores it, which is why a forgotten one does not break the site but must still be removed before you commit.
Two Console Methods Worth Knowing
console.table(list) prints an array of objects as a real table, far easier to scan than a wall of logged objects. console.count(label) prints how many times that line has run with that label.
Five letters, five redraws. With a list of two thousand rows, that is how an app starts to lag, and console.count is how you catch it. The fix is the debounce from Day 9.
Put a breakpoint inside your project's render function. Trigger it, then press F10 line by line and watch the Scope panel change. Then right-click the breakpoint, add a condition, and make it pause for only one item.
Picture the paused replay: every player frozen where they stand, and you stepping forward one frame at a time until you see the foul.
3. The Network Tab
Definition
The Network tab in DevTools lists every request your page makes, with its status, its headers and exactly what came back. When data does not appear, the question is always: did the request go out, and what came back? The Console cannot answer that. The Network tab can.
Real-Life Example
A dispatch rider's delivery log. When a parcel does not arrive, you do not guess. You check the log: was it picked up, was the address right, what did the person at the door say?
Types: What You See and What It Means
| What you see | What it means |
|---|---|
| No request at all | Your code never ran. Check the listener and look for an earlier error |
| Status 404 | The URL is wrong. Compare it character by character |
| Status 401 or 403 | The key or token is missing, wrong, or not permitted |
| Status 500 | Their server broke. Not your bug, but handle it |
| Status 200 but nothing renders | The data arrived in a different shape. Open the Response tab and look |
| (failed) plus a CORS message | CORS, from Day 8. Not fixable from the frontend |
| Pending forever | No timeout, and the server never answered |
Both examples below use the practice API from Day 8. The first has a one-letter typo in the URL; the second gets a 200 and still shows nothing.
The body shows the courses sitting inside data. The fix is one word: read body.data instead of body.courses. You find it in ten seconds by looking, or in an hour by guessing.
Open Network, tick Preserve log, reproduce the problem, and read the failing row: its status, its Response tab and its Headers tab. Nine times in ten, "the API is not working" is a typo in the URL or a shape you assumed rather than checked.
Picture the delivery log: picked up or not, right address or not, and what the person at the door said. Read it before you blame the rider.
4. Testing, at Your Level
Definition
A test is a small piece of code that runs your function with a chosen input and checks the answer against the one you expected. You are not writing a test suite this month; you are learning to think in test cases, which is the part that carries over to every job.
Real-Life Example
A tailor measuring a customer. They do not only check that the trousers fit standing still. They ask you to sit, to bend, to lift your leg: the positions where a seam gives way.
A Whole Testing Tool in Six Lines
To compare two answers, even arrays and objects, we turn both into text with JSON.stringify and compare the text. If they differ, we print both, so the failure tells you what went wrong.
Six tests, and four are boundaries. That is deliberate: bugs cluster at the edges. Now watch a test catch one. Someone "tidies" the code and writes > where it said >=:
The ordinary cases pass. A student with exactly 50 would have been failed, and only the boundary test noticed.
Types: What to Test
| Category | Ask |
|---|---|
| The normal case | Does it work for ordinary input? |
| Boundaries | Exactly on the limit, and one either side |
| Empty | Empty string, empty array, zero, nothing typed |
| Wrong type | Text where a number was expected |
| Too much | A very long string, two thousand rows |
| Twice | Click submit twice quickly. Does it do it twice? |
The empty list gives null after JSON.stringify, because 0 divided by 0 is NaN (not a number). On screen that would read "Average: NaN". One line fixes it:
These categories are what a QA engineer calls edge cases, and the six-line check function is what a framework such as Vitest or Jest does with better output and a runner. Learn to think in cases now and the framework takes a weekend.
Pick one function from your own project. Write at least six checks for it using the table above: one normal case, the boundaries, empty, and wrong type. Count how many fail the first time.
Picture the tailor: standing still proves nothing. Sit, bend, lift your leg, because that is where the seam gives way.
5. Performance: The Three That Matter
Definition
Performance is how quickly your page loads and responds. Do not optimise things nobody notices. These three are the ones a beginner actually hits.
Real-Life Example
A shop with a slow till. Customers do not care how the till works inside; they care that the queue is not moving. Fix the thing that makes the queue slow, not the paint on the counter.
One: Rebuilding the DOM Inside a Loop
Every time you write element.innerHTML += row, the browser takes everything already in the element, turns it back into text, adds the row, and reads the whole lot again. So each row costs more than the one before it.
To make that cost visible in Node, where there is no page, we use a stand-in: a function that adds up how many characters it is handed, which is roughly how much the browser must re-read. Array.from({ length: 500 }, (_, i) => ...) builds an array of 500 items, calling the arrow function with each position i. toLocaleString() prints a big number with commas.
That is why "build one string, assign once" was a rule on Day 4. Here is the same thing in a real browser, timed with performance.now(), which returns the time in milliseconds.
Two: Work on Every Keystroke or Scroll
You saw console.count reach 5 for one five-letter word in Section 2. You already fixed this on Day 9: debounce the search, throttle the scroll, and use IntersectionObserver rather than measuring positions.
Three: Images
A 4 MB photo shown 400 pixels wide is still 4 MB downloaded on somebody's data bundle.
- Resize before uploading.
loading="lazy"so off-screen images wait until they are needed.widthandheightso the page keeps a space for the image and does not jump when it arrives.- Modern formats where you can. WebP is usually much smaller than the same JPEG.
Every performance change makes code harder to read. Run Lighthouse and the Performance tab first, find the actual slow thing, fix that, and measure again. Speeding up a function that runs twice while ignoring one that runs ten thousand times is the most common way to waste an afternoon.
Picture the slow till: fix the thing that holds up the queue, and leave the paint alone.
6. Fix This Application
Today's main exercise. Ask an AI for a broken app, then debug it without reading the code. This is the closest thing to real junior work you can generate for yourself.
- Save it, open it, and do not read the source. On a real job there is no answer key and often no readable code.
- Hunt: click everything, click twice, leave fields empty, use boundary values, and keep the Console and Network tab open the whole time.
- For each bug, write down the symptom, how you found it, the cause and the fix. That is a bug report, and it is what Day 20 will expect.
- Only then read the answer key. Two out of four is a normal first attempt; three is good; four means generate a harder one with six.
Do This Now
- Break your own project three ways and fix each one from the error message alone.
- Set a real breakpoint, step through with F10, then set a conditional breakpoint.
- Find one real request in the Network tab and read its status, Response and Headers.
- Write six checks for one of your own functions, including the boundaries and the empty case.
- Do the Fix This Application exercise and write a bug report for every bug you find.
Checkpoint
Q1. "TypeError: Cannot read properties of undefined (reading 'city')". What do you look at first?
Whatever is immediately before .city, because that is the thing that is undefined. The property named in the message only tells you where you were reaching.
Q2. Data does not appear and the Console is clean. Where do you look?
The Network tab. Did the request go out, what status came back, and what is in the Response tab? A clean console with no data usually means a 200 with a shape you did not expect.
Q3. Why test 49, 50, 69 and 70 rather than 20 and 80?
Because bugs live at boundaries. A > written where >= was meant shows up exactly on the edge and nowhere else.
Q4. Why is innerHTML += inside a loop slow?
Each assignment re-reads everything already in the element, so every row costs more than the one before. Build one string and assign it once.
Tick Before You Move On
- ☐ I read a full stack trace down to my own file and line
- ☐ I used a real breakpoint and stepped through with F10
- ☐ I set a conditional breakpoint at least once
- ☐ I found my API problems in the Network tab, not by guessing
- ☐ I wrote boundary and empty test cases for one of my own functions
- ☐ I found at least two of the four planted bugs without reading the code
Quick Recap
Read the whole error: type, message, then down the stack to your own file. Breakpoints show you everything at one moment; a log shows one thing. The Network tab answers "did it go out, and what came back?" Test boundaries, empties and doubles, because that is where bugs live. Measure before optimising, and fix the thing that actually runs often.
Final Scenario
A tutor messages you at 9pm: "The average on Musa's class page says NaN, and only his."
You do not change a single line yet. You open the page with the Network tab open and see a 200. You open the Response tab and see that his class has no scores yet: an empty array, and 0 divided by 0 is NaN. You write a check for average([]), watch it fail, add the guard, and watch it pass.
Ten minutes, no guessing, and a test that makes sure it never comes back. That is debugging.
Tomorrow, Day 15: your second solo project, and the document you write before you write any code.