Every JavaScript developer, from the first week to the tenth year, spends a lot of time asking the same question: why is my code not doing what I think it is doing? The fastest way to answer it is rarely to stare harder at the code. It is to make the code tell you what it is doing, and the console is how it talks.
This guide covers how to read JavaScript error messages, how to log values in ways that actually help, and how to recognise the handful of bugs that cause most problems. Every example can be pasted into the JavaScript Compiler and run with Ctrl+Enter.
Read the error message first
When JavaScript fails, it usually tells you why, in one line. Learning to read that line is the single biggest debugging skill. An error has three useful parts: the type, the message and the location.
| Error | What it usually means |
|---|---|
| ReferenceError: x is not defined | You used a variable that does not exist, often a typo or a variable declared in another scope |
| TypeError: Cannot read properties of undefined | You tried to read a property of something that is undefined, such as user.name when user is undefined |
| TypeError: x is not a function | You called something that is not a function, often a misspelled method or a value you expected to be a function |
| SyntaxError: Unexpected token | The code is not valid JavaScript, often a missing bracket, quote or comma just before the reported spot |
| RangeError: Maximum call stack size exceeded | A function keeps calling itself without stopping |
The location points to where JavaScript noticed the problem, which is not always where the mistake is. "Cannot read properties of undefined" is reported where you read the property, but the real bug is wherever the value should have been set.
Log values in ways that help
console.log is the simplest debugging tool and still one of the best. A few habits make it far more useful:
Label your logs. A bare number in the console is meaningless ten logs later.
const total = 3 * 19.99;
console.log('total', total);
console.log({ total }); // the variable name becomes the label
Log objects, not strings. Logging an object shows its whole structure. The JavaScript Compiler prints objects and arrays as formatted JSON, so nested data is easy to read.
const order = { id: 42, items: [{ sku: 'A1', qty: 2 }], paid: false };
console.log('order', order);
Use warn and error for importance. console.warn and console.error stand out in the output, which helps when you are looking for the one line that matters.
Log before and after. When a value changes unexpectedly, log it before and after each step that touches it. The step where it changes is your bug.
The bugs behind most problems
Undefined values
Most "cannot read properties of undefined" errors come from data that is not there yet or not where you think it is:
const users = [{ name: 'Asha' }, { name: 'Ravi' }];
const user = users.find(u => u.name === 'Arjun');
console.log(user); // undefined: no match
console.log(user?.name ?? 'Not found'); // safe access with a fallback
Optional chaining (?.) and the nullish coalescing operator (??) prevent the crash, but ask why the value is missing as well.
Equality surprises
JavaScript's loose equality (==) converts types before comparing, with surprising results:
console.log(0 == ''); // true
console.log('1' == 1); // true
console.log(null == undefined); // true
console.log('1' === 1); // false
Use strict equality (===) by default. It compares both value and type.
Asynchronous order
Code that waits for something (a timer, a network request, a promise) runs later, not in the order it is written:
console.log('1: start');
setTimeout(() => console.log('3: timer'), 0);
Promise.resolve().then(() => console.log('2: promise'));
console.log('1b: end of script');
Run it and the order is start, end of script, promise, timer. If a value is undefined when you log it, check whether the code that sets it has actually run yet. With async functions, await the result before using it.
Scope and closures
Variables declared with let and const exist only inside the block where they are declared. Using var, or forgetting to declare a variable at all, causes values to leak or be shared in unexpected ways, a classic source of bugs in loops. Prefer const, and let when the value must change.
Reproduce the bug in isolation
When a bug appears inside a large application, copy the smallest piece of code that shows the problem into a clean environment. The JavaScript Compiler starts every run in a fresh, sandboxed frame, so nothing from a previous run or the rest of your app interferes. If the bug disappears in isolation, it comes from how the snippet interacts with the rest of your code. If it stays, you have a small example you can fix quickly, or share when asking for help.
For code that builds or changes a page, use the Live HTML Viewer, which shows the rendered page alongside its console. Our guide on testing HTML, CSS and JavaScript online covers that workflow.
A debugging routine that works
- Read the error message and note the type, message and line.
- Reproduce the problem reliably, ideally in a small snippet.
- Form a guess about the cause.
- Log to test the guess: the values going in, the values coming out.
- Change one thing and run again.
- Remove your debug logs when the bug is fixed, or turn them into proper error handling.
Changing several things at once is the most common way to "fix" a bug without understanding it, and to bring it back later.
Things to watch out for
- Infinite loops freeze the browser tab, in any environment. While experimenting, add a counter that stops a loop after a set number of iterations.
- Features vary by browser version. If something works for you but not for a user, compare browsers; What Is My Browser shows exact versions.
- Do not paste code you do not understand into consoles on sites where you are logged in. The JavaScript Compiler runs code in an isolated sandbox that cannot read this site's cookies or storage, but other consoles may not be protected.
Frequently asked questions
Can I use async/await in the JavaScript Compiler?
Yes. Logs from asynchronous code appear when they run.
Why does my DOM code show nothing?
The compiler's sandbox has an empty page. Use the Live HTML Viewer for code that builds a page.
Can I import npm packages?
Not directly; it runs plain browser JavaScript.
Why does the error line number look off?
Code runs inside a small wrapper, so line numbers in uncaught errors can be approximate. Use labelled logs to narrow it down.
Debugging is a skill, not luck. Read the error, log with labels, reproduce in isolation and change one thing at a time. Paste the examples above into the JavaScript Compiler and watch the console explain your code back to you.




