JavaScript Console Table
| Method | What it does | Field note |
|---|---|---|
console.log(x) | Print the value | Objects log LIVE - expand later shows FINAL state, not the snapshot |
console.table(arr) | Array of objects as grid | The underrated one - 50 rows readable instantly |
console.warn / error | Tagged severity | Error stacks carry click-to-source in devtools |
console.time(label) / timeEnd | Wall-clock timing | Pair by label; Performance.now() is the precise sibling |
console.group / groupEnd | Collapsible nesting | groupCollapsed starts shut - bundle related logs |
console.dir(obj) | Object inspector | Shows the OBJECT tree (DOM nodes especially) instead of HTML |
console.assert(cond, msg) | Log only on failure | Silent when true - inline sanity checks |
console.trace() | Stack at this spot | Who called this? - one line answers it |
The console is the debugger you always have open, and its method list is bigger than log and error: console.table renders an array of objects as a sortable grid, dir opens an object tree instead of HTML markup, time/timeEnd bracket wall-clock costs, and trace prints the call stack on demand.
Bottom line: the trap that costs real debugging time is that console.log(obj) logs a LIVE reference - when you expand the object in devtools, you see its FINAL state, not the state at log time, so async mutations look like time travel. Snapshot it with JSON.parse(JSON.stringify(obj)) or structuredClone when the moment matters.
The honest part: console calls are development scaffolding, not telemetry - they cost time in hot loops and leak internals to anyone opening devtools. Strip or gate them for production (a no-op logger wrapper), and reach for the real debugger (breakpoints, watch expressions) once a problem needs more than two log lines.
How to use
- Inspect data grids with console.table(users) - columns, values, and index in one readable view; pass a property array to pick columns.
- Bracket costs: console.time('parse'); doWork(); console.timeEnd('parse') - same label both sides; for sub-millisecond precision use performance.now().
- Answer 'who called this' with console.trace() - the full stack at that spot, no breakpoint required; nest related output in console.group for collapsible sections.
Frequently asked questions
Why does my logged object show values from LATER in the code?
The log stores a live reference, not a copy: devtools renders the object's CURRENT state when you expand it in the panel, which can be minutes after the log line executed. Mutations that happen after the log - async updates, loops, other handlers - all show up, making the printed value look like it time-traveled. The fixes: snapshot at log time with JSON.parse(JSON.stringify(obj)) (structured-safe, cheap for small objects), use structuredClone(obj) for a deep copy, or set a breakpoint - the debugger pauses real state, no reference games.
When is console.table worth a line over console.log?
For any array of similarly-shaped objects: console.table(users) draws an index-by-column grid you can sort by clicking - instantly readable where the object-tree view of fifty items is a scroll marathon. It also accepts an array of primitives as a neat two-column listing, and a second argument selects which properties to show (console.table(users, ['name', 'role'])). Below roughly five rows it is not worth it; above ten it usually pays for itself - and it degrades to plain output in environments without a graphical console.
What is the difference between console.log and console.dir on a DOM element?
log renders the element as HTML markup (the tag view), dir renders it as an expandable OBJECT - properties, methods, the internal structure. When you need $0's JS surface (children list, dataset, listeners via getEventListeners in devtools), dir is the view that shows it. The same split applies to any object with a custom toString: log may print a formatted summary where dir always gives the property tree. For plain data objects the two converge, which is why the distinction only surfaces when debugging DOM and class instances.
How do console.assert and console.trace fit real workflows?
assert is an inline sanity check that stays silent while true: console.assert(items.length > 0, 'empty items before render') costs nothing when the world is fine and prints a failure message with the stack when it is not - lighter than throwing for conditions that should never happen but should not crash a render either. trace is the 'who called this' button: drop it in a function that gets called from somewhere unexpected and read the exact path without setting a breakpoint. Both shine when you cannot or do not want to pause execution - build scripts, request handlers, animation frames.