JavaScript Debugging Tools
Quick debugging one-liners for logging, tracing, timing, and inspecting JavaScript values in any project.
TL;DR
- 01Log values with
console.log()and inspect withconsole.dir(). - 02Pause execution with the
debuggerstatement in DevTools. - 03Measure performance with
console.time()andconsole.timeEnd()using matching labels.
Tips
- 01Use
console.table()for arrays of objects, since it shows each property as a column for fast visual scanning. - 02Use the
%cformat specifier inconsole.log()to add custom CSS styling to messages, making important output easier to spot.
Warnings
- 01Remove
debuggerstatements and console logs before shipping production code, since they slow down your app and expose data. - 02
console.log()shows a live reference to objects, so an expanded log can differ from the object's state at log time.
Logging Basics
console.log()The obvious choice, but several other console methods solve specific logging problems faster.
console.log('user:', user);Labeled logsLabel your logs by passing a string prefix as the first argument.
console.log('typeof myVar:', typeof myVar);console.table()Displays an array of objects as a clean, scannable table instead of logs.
console.table(users);Combined valuesCombine multiple values in one log call to compare them side by side.
console.log('before:', before, 'after:', after);%o SpecifierEmbeds an inspectable object directly inside a formatted log string.
console.log('user: %o', user);Pausing and Tracing
debugger;A single statement pauses your entire script wherever DevTools is open.
debugger;console.trace()Prints the full call stack leading up to the current line of code.
console.trace();Conditional debuggerPauses only when a condition is true, instead of every single time.
if (i === 5) debugger; // pause onceconsole.assert()Logs a message only when the given condition turns out false.
console.assert(user.id, 'Missing user id');console.count()Counts and logs how many times a labeled line has run.
console.count('render'); // render: 1Checking Values
Strict equalityMost "why is this undefined" bugs resolve faster with a strict equality check.
console.log(myVar === undefined);Nullish coalescingLog a fallback when values are missing using the ?? operator.
console.log(myVar ?? 'fallback');Falsy checkDetect any falsy value with a simple negation check inside an if.
if (!myVar) console.log('Falsy!');typeofUse typeof when results seem off, to confirm the value's data type.
typeof 'hi' === 'string'; // trueNumber.isNaN()Checks for NaN without the risky type coercion of the global isNaN().
Number.isNaN(NaN); // true
Number.isNaN('x'); // falseSnapshotting Live Objects
JSON.stringify snapshotconsole.log(obj) shows the object's state when expanded, not when logged — this fixes that.
console.log(JSON.stringify(obj, null, 2));structuredClone()Takes a frozen deep copy for logging without mutating the live original.
console.log(structuredClone(state));console.dir()Renders a DOM node as an interactive property tree instead of HTML.
console.dir(document.querySelector('button'));Timing and Errors
console.time/timeEndA matching label is the only thing connecting console.time() to its console.timeEnd().
console.time('label');
// code here
console.timeEnd('label');console.group()Nests related log messages in a collapsible group for easier scanning.
console.group('info');
// related logs...
console.groupEnd();try...catchWrap risky code in a try...catch block to handle errors safely.
try {
riskyFunction();
} catch (e) {
console.error('Error:', e.message);
}console.error()Prints errors in red with a full, clickable stack trace attached.
console.error('Failed to save:', err);console.warn()Flags non-fatal issues in yellow so they still catch your eye.
console.warn('Approaching API rate limit');In Practice
Combines console.time, console.table, and try/catch to time a fetch call, inspect its output, and catch failures.
- 01console.time('loadUsers') starts a labeled timer right before the fetch begins.
- 02console.table(users) renders the array of user objects as a scannable table instead of a nested log.
- 03The try/catch block logs a clear error message if the fetch or parsing fails.
- 04finally guarantees console.timeEnd('loadUsers') runs and reports the elapsed time either way.
async function loadUsers() {
console.time('loadUsers');
try {
const res = await fetch('/api/users');
const users = await res.json();
console.table(users);
return users;
} catch (error) {
console.error('Failed to load users:', error.message);
return [];
} finally {
console.timeEnd('loadUsers');
}
}
loadUsers();
// logs a table of users, then "loadUsers: 42.3ms"FAQ
console.log() prints a string representation of a value and is best for primitives and quick output. console.dir() renders an interactive property tree instead, which is far more useful for inspecting DOM nodes or complex objects. Use console.dir() when you need to drill into nested properties.
Add the debugger statement directly in your code. When DevTools is open and execution reaches that line, it pauses automatically. From there you can step through the call stack, inspect variables, and evaluate expressions in the console.
Wrap the code with console.time('label') before it runs and console.timeEnd('label') after. The browser logs the elapsed milliseconds to the console using the matching label. This identifies which measurement the output belongs to.
console.log() captures a live reference to objects, so expanding the output later shows the current state, not the logged state. Use console.log(JSON.stringify(obj)) or console.dir() to snapshot the value at the moment of logging.
In DevTools, open the Sources panel and enable 'Pause on exceptions' (the stop-sign icon). The debugger automatically breaks at the exact line that throws. This lets you inspect the local scope and call stack without manually adding breakpoints.