Boost your JavaScript skills by learning advanced debugging and error handling strategies, including try-catch blocks and console usage for smoother coding.
Your code just broke, and the browser is yelling at you in red. The instinct is to panic, or to start changing things at random until it stops complaining. Resist that. On Day 10 of the 30 Days of JavaScript series we slow down and read what the error is actually telling us, then learn to handle the ones we can see coming. That is the difference between guessing and debugging.
Table of Contents
- Deciphering Errors
- Using the Console
- Working with Breakpoints
- Error Handling with Try-Catch
- Custom Exceptions
- Advanced Debugging Techniques
- Wrapping Up
Deciphering Errors
An error message is a clue, not an insult. JavaScript tells you the type and, most of the time, the line. A SyntaxError means the engine couldn’t even parse your code. A ReferenceError means you reached for a variable that doesn’t exist. A TypeError means the value is there but you’re using it wrong, like calling something that isn’t a function. Read the type first, then the line, before you touch anything.
For example, consider this code:
/**
* Logs a message to the console, but this will result in a SyntaxError due to missing quotes.
*/
console.log(Hello, World!);This throws a SyntaxError because the text was never quoted, so the parser chokes before the code runs. Once you recognize the shape of that message, you’ll spot the missing quotes in seconds.
Using the Console
The console is the cheapest debugging tool you own, and often the fastest. Drop a console.log() next to a value and you can watch what your code is really doing instead of what you assume it’s doing. Track a variable, confirm a branch ran, test a one-line snippet right in the browser.
Consider this loop:
/**
* Calculates the running sum and logs the result at each iteration using console.log().
*
* @returns {void}
*/
let sum = 0;
for (let i = 0; i <= 10; i++) {
sum += i;
console.log(`Sum so far: ${sum}`);
}Printing the sum on every pass lets you see the loop build up step by step. If the math ever drifts, you’ll know exactly which iteration it happened on. When you want more than plain text, console.table() and console.error() are worth a look too.
Working with Breakpoints
When logging isn’t enough, breakpoints let you freeze time. Set one and execution pauses on that line so you can inspect every variable in scope, then step through the code one statement at a time. Chrome and Firefox both ship this in their built-in dev tools, no setup required. For a stubborn bug, watching the state change line by line beats sprinkling logs everywhere.
Error Handling with Try-Catch
Debugging is what you do while building. Error handling is what protects your users when something you didn’t predict happens in production. JavaScript’s try…catch block lets you wrap risky code, catch the failure, and keep the app running instead of crashing the page. You can add a finally block too, which runs either way, handy for cleanup like closing a connection.
Here’s a basic example:
/**
* Demonstrates the use of try-catch for handling a ReferenceError.
*
* @returns {void}
*/
try {
let name = 'John Doe';
console.log(nam); // ReferenceError: "nam" is not defined
} catch (error) {
console.log('An error occurred:', error.message);
}The typo nam throws a ReferenceError, but the catch block grabs it and reads error.message instead of letting the whole script die. The caught object also carries error.name and a full error.stack when you need more context.
Custom Exceptions
Sometimes the failure isn’t a JavaScript error at all, it’s a business rule. When a condition your app cares about isn’t met, use throw to raise your own error and route it straight into the same catch flow.
For example, let’s enforce an age restriction:
/**
* Throws a custom error if the age restriction is not met.
*
* @throws {Error} - If age is less than 18.
* @returns {void}
*/
try {
let age = 17;
if (age < 18) {
throw new Error('Age restriction not met.');
}
console.log('Access granted.');
} catch (error) {
console.log(`Error: ${error.message}`);
}Here the age check fails, so you throw an Error with a clear message and handle it in one place. When you need to tell different failures apart, you can throw built-in subclasses like TypeError or RangeError, and check error.name in the catch.
Advanced Debugging Techniques
As an app grows, you want to catch problems before they ever run. A linter like ESLint flags dead variables, unreachable code, and shaky patterns while you type. Browser dev tools go further: inspect the live DOM, watch network requests, and profile performance to find what’s actually slow instead of what you suspect is slow. These aren’t beginner tools, but they pay off the moment a bug stops being obvious.
Wrapping Up
Good debugging starts with reading the error, not fearing it. Once you know a SyntaxError from a ReferenceError from a TypeError, the console and breakpoints give you a clear view into what your code is doing at every step.
From there, try…catch keeps runtime failures from taking the whole page down, and throw lets you enforce your own rules with the same machinery. Handled well, errors become a normal part of the flow instead of a fire drill.
Fold in a linter and your browser’s dev tools as your projects get bigger, and you’ll spend less time hunting bugs and more time shipping. That’s the whole point: cleaner code, fewer surprises, and an app you can trust in production.
What’s Next?
On Day 11 we dig into Performance Optimization: making your JavaScript run faster, use less memory, and stay responsive as it grows. See you there.


