JavaScript Console Table

MethodWhat it doesField note
console.log(x)Print the valueObjects log LIVE - expand later shows FINAL state, not the snapshot
console.table(arr)Array of objects as gridThe underrated one - 50 rows readable instantly
console.warn / errorTagged severityError stacks carry click-to-source in devtools
console.time(label) / timeEndWall-clock timingPair by label; Performance.now() is the precise sibling
console.group / groupEndCollapsible nestinggroupCollapsed starts shut - bundle related logs
console.dir(obj)Object inspectorShows the OBJECT tree (DOM nodes especially) instead of HTML
console.assert(cond, msg)Log only on failureSilent when true - inline sanity checks
console.trace()Stack at this spotWho called this? - one line answers it
Reference: the MDN console reference. The console trap that costs real debugging time: console.log(obj) logs a LIVE reference - devtools shows the object's FINAL state when you expand it, not its state at log time, which is why async mutations look like time travel. Fix: log(JSON.parse(JSON.stringify(obj))) for a snapshot, or use the structuredClone route. Bottom line: console.table for arrays of objects, console.dir for DOM nodes (HTML vs internals), console.time pairs for coarse timing, and console.trace answers 'who called this' in one line. Related tools: JSON table (the snapshot stringify trick), error table (what error severity means downstream), and event loop table (why async logs arrive out of order).

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

  1. Inspect data grids with console.table(users) - columns, values, and index in one readable view; pass a property array to pick columns.
  2. Bracket costs: console.time('parse'); doWork(); console.timeEnd('parse') - same label both sides; for sub-millisecond precision use performance.now().
  3. 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.

Related tools