Console beyond log
Seven console methods that turn debugging output from a wall of text into a workbench.
console.log is the workhorse, and it's almost always enough. But the moment you're logging an array of objects, or a hundred messages from inside a loop, or a function whose values you want to time, the output becomes harder to read than the code that produced it. The console becomes the bottleneck.
The console object has more than just log. Each of the methods in this lesson is a small upgrade — none of them are clever, but knowing which one to reach for turns a session of squinting at JSON dumps into a session of actually reading the data.
console.table — render arrays of objects as a table
console.log on an array of objects gives you a vertical list, one expandable bracket per item. To compare any field across rows, you have to expand each one and read.
console.table does the obvious thing: shows it as an actual table.
const orders = [
{ id: "A1", customer: "Liu", total: 42, status: "shipped" },
{ id: "A2", customer: "Patel", total: 18, status: "refunded" },
{ id: "A3", customer: "Carlson", total: 88, status: "shipped" },
];
console.table(orders);The console renders this as a five-column grid (an (index) column plus the four object keys). Click any column header to sort. You can pass a second argument — an array of column names — to limit which fields show up:
console.table(orders, ["customer", "total"]);
Now you only see two columns. This is the function to reach for any time you have an array of similar objects and want to compare them side by side.
console.table works great on the result of array methods. console.table(users.filter(u => u.age > 18)) shows you the filtered set as a sortable table without any extra code.
console.group — collapse related logs together
When a function logs five things, and you call it ten times in a loop, you have fifty log lines in the console with no visual grouping. console.group puts a label on a collapsible block — everything logged between group and groupEnd becomes one indented section.
for (const order of orders) {
console.group("Processing " + order.id);
console.log("Customer:", order.customer);
console.log("Total:", order.total);
console.log("Status:", order.status);
console.groupEnd();
}Each iteration shows up as one collapsed line in the console — Processing A1, Processing A2, and so on. Click one to expand and see its three child logs.
console.groupCollapsed is the same thing but starts collapsed by default — useful for the noisy logs you don't want to see unless you're looking for them.
console.time — measure how long something took
The pattern of "log a timestamp before, log one after, subtract them in your head" is almost always wrong. You measure too coarsely, you forget which timestamp is which, and you can't easily compare across runs.
console.time(label) and console.timeEnd(label) measure the gap automatically and print it in milliseconds.
console.time("search");
const matches = posts.filter(p => p.title.includes(query));
console.timeEnd("search");
// console output: search: 12.4 msThe label is the connective tissue — time("x") and timeEnd("x") find each other by name, so you can run multiple timers in parallel without confusing them.
For more precise measurement (microseconds, not milliseconds, plus the ability to record in the Performance panel), use performance.now() and performance.mark(). Lesson 6 covers those. console.time is the right tool for casual "how long does this take?" checks during development.
email field is misformatted. Which method gives you the fastest scan?console.trace — log the call stack at a point in code
console.trace(label) logs a message and the call stack that led to it. Use it when you want to know "who called this function?" without setting a breakpoint.
function syncWithServer() {
console.trace("syncWithServer called");
/* ...real work... */
}The console shows the label, then the stack underneath it: syncWithServer @ sync.js:24, then handleClick @ ui.js:88, and so on. Each entry is clickable — it jumps you to that line in Sources.
When to reach for trace: a function is being called more often than you expected, and you want to know who's calling it without pausing the page.
console.dir — log the object's structure, not its rendering
console.log(domElement) prints the element as it would appear in the Elements panel — <div class="card">…</div>. That's usually what you want for a DOM node.
console.dir(domElement) prints the element as a JavaScript object, with all its properties: tagName, className, every inherited property, every event handler. It's the difference between "show me what this element is" and "show me what's on this object".
const button = document.querySelector(".cta");
console.log(button); // <button class="cta">Sign up</button>
console.dir(button); // expandable property tree: tagName, className, onclick, ...For non-DOM objects the difference is small (both are object trees). For DOM elements, dir is the one that gives you the inspectable property tree.
console.assert — only log when something's wrong
console.assert(condition, message) logs the message only when the condition is false. Otherwise it does nothing.
function processOrder(order) {
console.assert(order.total > 0, "Order has zero or negative total:", order);
/* ...the rest of the function... */
}If order.total > 0 is true, the assert is silent — your console isn't polluted with "everything's fine" messages on every order. The moment a bad order shows up, the assert fires with a stack trace.
It's a quiet way to add invariant checks during development. assert failures are styled like errors (red text, Assertion failed: prefix), so they stand out from regular logs.
console.assert doesn't throw. The function continues running after the assert fires. Use it for visibility, not for guarding against bad input — for that, throw a real error.
%c — style log output with CSS
console.log accepts a special token, %c, which says "apply this CSS string to the next chunk of text." It's the easiest way to make a log line stand out in a sea of others.
console.log( "%c Auth flow %c started", "background: #c96442; color: white; padding: 2px 6px; border-radius: 3px;", "color: gray;" );
Each %c consumes one CSS string from the trailing arguments. The first chunk gets the first style, the next gets the next, and so on.
This is purely a visual aid — useful for tagging logs from different subsystems with distinct colors so they're recognizable at a glance. Don't go overboard; a console where every log has a different style is just as noisy as one where none of them do.
console.log(button). You see the rendered HTML, but you wanted to see all the properties — its onclick, dataset, computed style, etc. What do you switch to?