console.log is how you find out what your JavaScript actually did. It prints a value to the debugging console in a browser and to standard output in Node.js, and everything else about it is a small set of rules worth learning once: what each method is for, what comes back, and why the same object can look different in two different environments.
What does console.log actually do?
It writes a line to the console. That is all. There is no magic in it, no formatting engine beyond a couple of legacy placeholders, and no return value worth keeping. What makes it useful is that it is available in every JavaScript host without importing anything: the browser exposes console on the global object, and Node.js does the same.
Your first script, then, is three lines long:
const name = "Ada";
const score = 97.5;
const passed = true;
console.log("Name:", name);
console.log("Score:", score);
console.log("Passed:", passed);
Save it as intro.js and run node intro.js, or paste it into the console of any browser page. Either way you get the same three lines:
Name: Ada
Score: 97.5
Passed: true
Notice the comma after each label. console.log accepts any number of arguments and prints them separated by a space, so console.log("Name:", name) is the normal way to label a value. It is not string concatenation — nothing is converted until it reaches the screen.
Which console method should you reach for?
The console object carries a small family of methods that differ mostly in how they are labelled and where they go. That labelling matters in a browser, because the console panel can filter by severity.
| Method | Use it for | Browser behaviour |
|---|---|---|
console.log | General output | Normal level, shown by default |
console.info | Informational notes | Same as log but tagged as info |
console.warn | Something suspicious | Warning level, often yellow |
console.error | Something failed | Error level, often red, with a stack trace |
console.debug | Verbose detail | Hidden unless you enable verbose logging |
console.table | Arrays of similar records | Renders an aligned table |
console.dir | Deep inspection of one value | Expands the object tree |
console.count / time | Counting calls, timing a block | Prints a running total or elapsed time |
console.group | Grouping related output | Collapsible headings |
Here are four of them in one run:
console.log("a plain message");
console.info("an informational message");
console.warn("something looks wrong");
console.error("something broke");
const result = console.log("this line still logs");
console.log("console.log returned:", result);
The last two lines matter more than they look. console.log returns undefined, so assigning it to a variable gives you undefined — it is a printing function, not a producer of values. If you ever find yourself writing const id = console.log(id), you have misunderstood what it does.
One environment difference is worth knowing early. In a browser all of these land in the same console panel, distinguished by level. In Node.js, console.warn and console.error are written to standard error while console.log and console.info go to standard output, so you can separate them in a shell with node app.js 2>errors.txt.
Why does the same object look different in two environments?
Because the two consoles choose different representations, and neither is wrong.
const user = { name: "Ada", role: "admin" };
const scores = [12, 30, 7];
console.log("User:", user);
console.log("Scores:", scores);
console.log("Args:", 1, "two", true);
In a browser, console.log hands you a live, collapsible view of the object. That view updates if the object changes later, which is helpful while debugging and confusing when you are trying to see the value at a specific moment. In Node.js the same call prints a flat snapshot taken at the moment of the call.
When you need the value at a moment in time rather than a live view, console.dir is the tool that gives you a stable deep expansion, and console.table turns a list of similar records into something you can read at a glance:
const rows = [
{ course: "HTML", hours: 4 },
{ course: "CSS", hours: 6 },
{ course: "JavaScript", hours: 12 },
];
console.table(rows);
console.dir(rows[0]);
The console.dir call prints the first record expanded, with its two properties listed. The table call above it prints one row per course with the two columns aligned — much easier to scan than three separate console.log calls.
What about the %s and %d placeholders?
The console understands a small, legacy set of format specifiers. %s substitutes a string, %d a number, %i an integer, %f a float, %j JSON, and %o and %O objects. A doubled %% renders a single literal percent sign.
console.log("%s scored %d points", "Ada", 97);
console.log("%s scored %d points", "Grace", 89);
console.log("%d%% complete", 50);
Run that with node and it prints Ada scored 97 points, then Grace scored 89 points, then 50% complete. The specifiers are positional: the first %s takes the first argument after the format string, the first %d takes the second, and so on. If you supply fewer arguments than placeholders, the unmatched placeholders are left in place rather than throwing.
These are worth knowing, but they are not the first tool to reach for. In modern code, a template literal usually reads better and cannot get its argument order wrong:
const name = "Ada";
const points = 97;
console.log(`${name} scored ${points} points`);
Should console.log stay in production code?
Not usually, and the reason is not style. Every console call costs a little work: the value has to be serialised for display, and in a browser with the developer tools closed the call is often a near no-op but not always. On a hot path, thousands of log lines are a real cost. Beyond cost, a log line can quietly leak personal data into logs you did not mean to write.
The practical approach is to leave diagnostics in while you develop, then remove them before the code ships, or route them through a tiny helper you can switch off in one place:
const debug = process.env.NODE_ENV !== "production";
function trace(label, value) {
if (debug) console.log(`[${label}]`, value);
}
trace("cart loaded", { items: 3 });
Note what that snippet does in a plain Node run: process.env.NODE_ENV is undefined when you have not set it, so debug is true and the trace prints. Set NODE_ENV=production in the environment and the helper goes quiet without any change to the code that calls it.
What to remember about console.log
console.logprints to the console in a browser and to standard output in Node.js. It returnsundefined.- Pass several arguments with commas to label values. Nothing is concatenated for you.
- Use
warnanderrorfor severity so the browser can filter them, andtableanddirwhen a raw print is unreadable. - Format placeholders like
%sand%dstill work, but a template literal is harder to get wrong. - A browser console shows live objects; Node prints a snapshot. Use
console.dirwhen you want a stable expansion.
Try it yourself: write a script that stores an order in an object and prints it three ways — with console.log, with console.dir, and with console.table over an array holding that same object. Run it with node order.js, then paste the file into a browser console and run it again. Compare the two renderings side by side and note exactly which parts differ; that difference is the environment, not your code.