Before you begin
Why this matters
Most of what you learn about a running program, you learn from what it prints. If you know exactly which characters a print writes, you can trust what you see, and notice when a value is not what it looks like.
What this helps you reason about
- Predict the exact text of a console.log line, including its spaces and line ends.
- Line up numbers in a column with a fixed number of decimal places.
- Spot a hidden space in a string, and an empty answer that became 0 without any error.
Printing is how a program shows you what it is doing, and input is how it gets text from a person. This lesson shows exactly what console.log writes, how to line up numbers, how to see a string character by character, and how a typed line becomes a number.
Already know this? The short version
- console.log writes its values with one space between each pair and always ends the line; + joins strings with nothing in between. process.stdout.write writes text without a line end.
- toFixed(1) gives a string with one decimal place. padStart(6) and padEnd(8) add spaces up to a minimum width, and padStart(2, "0") adds zeros; a longer value is never cut.
- A printed string shows no quotes, so a space or line end at its end is invisible; JSON.stringify(value) shows it with quotes and \n. console.error writes to a separate output, which the stepper does not show.
- readline gives a typed line without its line end. Number(" 21 ") is 21, but Number("") is 0, so refuse an empty line before you convert, and check the result with Number.isInteger.
Words in this lesson (6)
- newline
- The character that ends a line of text, written \n in code. A print that ends the line writes a newline after its text.
- padding
- Extra characters, usually spaces, added to a value to fill a minimum width. A value longer than the width is never cut.
- debug form
- A way of writing a value that shows exactly what it holds, for the programmer: text between quotes, with a hidden character such as a newline written as \n.
- standard output and standard error
- The two output streams every program starts with. Standard output is for normal results; standard error is for messages about problems.
- standard input
- The input every program starts with. In a terminal, it carries the lines the user types; each line ends with a newline when Enter is pressed.
- parse
- To read text and build a value from it, such as the number 21 from the text "21". Parsing fails when the text does not have the expected form.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / Printing
What exactly does a print write?
console.log(…) prints its values on one line. Between each pair of values it puts one space, and at the end it writes a newline: the character that ends a line, written \n in code. Each console.log call therefore prints a whole line, so a label and its value belong in one call.
A string built with + is different. + joins two strings exactly as they are, with nothing in between. "room: " + room + "temp: " + temp has no space before temp:, so the words run together.
console.log always ends its line. To write text without a line end, Node.js has process.stdout.write("kitchen "), which writes exactly the text you give it.
const room = "kitchen";
const temp = 21.5;
console.log("room:", room, "temp:", temp);
// room: kitchen temp: 21.5
console.log("room: " + room + "temp: " + temp);
// room: kitchentemp: 21.5 console.log adds a space between values and a newline at the end; + adds nothing.
Coming from Python: console.log("a", "b") adds a space as print does, but it has no end="" option: every call ends its line.
Precisely: process.stdout.write in the stepper
process is a name that Node.js provides. The stepper checks TypeScript without Node’s type definitions. So process.stdout.write gives a compile error in the stepper. In a Node.js project with the @types/node package, it works. I/O, buffering, and external state looks at when written text reaches the screen.
Experiment
Find the spaces that console.log adds
console.log writes its values with one space between each pair and a newline at the end. + builds one string from its parts and adds nothing, so a missing space stays missing.
Why it matters Give each line its own console.log call, and check every + for the space it needs.
Predict both output lines, space by space. Then replace line 3 with two lines, console.log("room:", room); and console.log("temp:", temp);, and predict how many lines are printed.
const room = "kitchen";
const temp = 21.5;
console.log("room:", room, "temp:", temp);
console.log("room: " + room + "temp: " + temp);
What to look for
Step and watch the output panel. The stepper sometimes stops on the same line twice; that is normal for this debugger. The variables panel shows room as "kitchen": the quotes only mark where the text starts and ends. Line 3 prints room: kitchen temp: 21.5, with one space between each pair of values. Line 4 prints room: kitchentemp: 21.5, because + joins the strings with nothing in between. After the edit, the output has three lines: room: kitchen, temp: 21.5, and the line built with +.
Expected output
room: kitchen temp: 21.5
room: kitchentemp: 21.502 / Formatting
How do you set the width and the decimal places?
A number on its own is printed in its shortest form: 7 prints as 7 and 21.5 as 21.5. In a column, the numbers then start at different places.
toFixed(1) turns a number into a string with exactly one decimal place: (7).toFixed(1) is "7.0", and (19.04).toFixed(1) is "19.0". The digit is rounded, not cut. The parentheses around 7 are needed because 7.toFixed would read the dot as a decimal point.
Padding then gives every string the same width. Padding is extra characters, usually spaces, added to fill a minimum width. padStart(6) adds spaces in front until the string is 6 characters long, so numbers line up on the right. padEnd(8) adds spaces after a label. padStart(2, "0") pads with zeros instead: "5" becomes "05". A value that is already longer than the width is never cut.
console.log("kitchen".padEnd(8) + (21.5).toFixed(1).padStart(6));
console.log("garage".padEnd(8) + (7).toFixed(1).padStart(6));
// kitchen 21.5
// garage 7.0 toFixed sets the decimal places and gives a string; padStart and padEnd set a minimum width.
Precisely: rounding the stored number
(1.005).toFixed(2) is "1.00", not "1.01": the number stored for 1.005 is slightly less than 1.005, as Equality and comparison explains for 0.1 + 0.2. toFixed rounds the stored value. An exact half rounds up: (18.25).toFixed(1) is "18.3".
Experiment
Line up a column of temperatures
A width is a minimum. Padding adds spaces up to it, and a value that is already longer is written in full. The decimal setting rounds the number to that many places.
Why it matters Choose a width at least as long as the longest value you expect. Otherwise the column no longer lines up.
Predict how many spaces come before 21.5, 7.0, and 19.0. Then change padStart(6) in the first line to padStart(2), and predict whether 21.5 is cut.
console.log("kitchen".padEnd(8) + (21.5).toFixed(1).padStart(6));
console.log("garage".padEnd(8) + (7).toFixed(1).padStart(6));
console.log("hall".padEnd(8) + (19.04).toFixed(1).padStart(6));
What to look for
Each line appears when the stepper moves past it. Every label is padded to 8 characters and every number to 6: kitchen 21.5, garage 7.0, hall 19.0. 7.0 gets its .0 from the decimal setting, and 19.04 is rounded to 19.0. The stepper stops on each line several times while padEnd, toFixed, and padStart run; that is normal for this debugger. After the edit, the first line is kitchen 21.5: the number keeps all four of its characters, and the column no longer lines up.
Expected output
kitchen 21.5
garage 7.0
hall 19.003 / Debug output
How do you see exactly what a value holds?
console.log("room: " + room) prints the characters of room and nothing else. If room is "kitchen ", with a space at the end, the screen shows room: kitchen , and the space is invisible. A comparison such as room === "kitchen" is then false for a reason you cannot see.
The debug form of a value is the way of writing it for a programmer who is looking for a mistake. It shows exactly what the value holds: a string between quotes, with a hidden character such as a newline written as two characters, \n. JSON.stringify(room) gives the debug form of a string: "kitchen ", with its quotes. When console.log prints an object, it already shows the strings inside it with quotes: { room: 'kitchen ', temp: 21.5 }.
Use the plain form for output that a user reads, and the debug form while you look for a mistake. Messages for the programmer often go to a different place. Every program starts with two output streams: standard output, for normal results, and standard error, for messages about problems. console.log writes to standard output and console.error to standard error. The stepper shows standard output only.
const room = "kitchen ";
console.log("room: " + room);
// room: kitchen
console.log("room: " + JSON.stringify(room));
// room: "kitchen "
console.log(room.length); // 8 The plain form shows the characters; the debug form shows exactly which characters there are.
Coming from Python: JSON.stringify(s) does the job of Python’s repr(s): it shows the quotes and the escapes.
Precisely: two debug forms
console.log formats an object with Node’s util.inspect, which quotes strings with single quotes and shortens deep or long values. JSON.stringify writes the format of JSON files, with double quotes. Neither form is meant for a user.
Experiment
Find the space you cannot see
Printing a string writes its characters, and a space or a newline has no visible mark of its own. The debug form puts quotes around the string and writes a newline as \n, so every character can be seen.
Why it matters When two strings look equal but compare as different, print both in a debug form before you change any code.
Predict whether the first output line shows the space at the end of room, and what line 4 prints. Then change "kitchen " to "kitchen\n" and predict the output again.
const room = "kitchen ";
console.log("room: " + room);
console.log("room: " + JSON.stringify(room));
console.log("length:", room.length);
What to look for
The variables panel shows room as "kitchen ", with quotes. Line 2 prints room: kitchen , and the space at the end cannot be seen. Line 3 prints room: "kitchen ", and line 4 prints length: 8, one more than the seven letters. After the edit, the first line is room: kitchen followed by an empty line: the newline inside room ends the line early. Line 3 prints room: "kitchen\n", and the length is still 8.
Expected output
room: kitchen
room: "kitchen "
length: 804 / Input
How do you read a line and turn it into a number?
A program reads what a person types from standard input: the input every program starts with. In a terminal, it carries the lines the user types, and each line ends when the user presses Enter.
In Node.js, the readline module reads one line at a time and removes its line end. The answer is always a string. To use it as a number, the program must parse it: read the text and build a value from it, which can fail. Number(text) parses the whole string, ignores spaces around it, and gives NaN, “not a number”, when the string is not a number. Number.isInteger(x) is true only for a whole number.
One case surprises many programmers: Number("") is 0. A user who presses Enter without typing anything sends an empty line, and Number turns it into 0, a number that looks valid. So check for an empty line first. Below, trim() removes spaces at both ends, || means “or”, and ! means “not”.
The stepper cannot type for you: it gives every program empty standard input, so a read finds the end of the input at once. So the experiment here uses a fixed text in place of the typed line.
const line = " 21 "; // as readline gives it
const text = line.trim();
const target = Number(text);
if (text === "" || !Number.isInteger(target)) {
console.log("Please type a whole number.");
} else {
console.log("target:", target); // target: 21
} Input is a string: trim it, refuse an empty line, then parse it and check the result.
Coming from Python: int("") raises a ValueError, but Number("") gives 0 with no error. So an empty answer looks like a valid number.
Precisely: reading a line in Node.js
In a Node.js project with @types/node, one way to ask for a line is the code below. await waits for the user to press Enter, and it needs the file to be a module. The line comes back without its line end.
import * as readline from "node:readline/promises";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const line = await rl.question("Target? ");
rl.close(); Experiment
Turn an empty answer into a number
Number reads the whole string, ignores spaces and line ends around it, and treats an empty string as 0. parseInt reads the digits at the start and gives NaN when there are none. Neither of them tells you that the user typed nothing.
Why it matters Check for an empty line yourself before you convert it, and check the number afterwards.
Predict what Number turns an empty string into, and whether the check on line 4 notices. Then change Number(line) to parseInt(line) and predict again.
const line = ""; // what readline gives when Enter is pressed alone
const target = Number(line);
console.log("target:", target);
console.log("a number:", !Number.isNaN(target));
What to look for
line shows "" in the variables panel, and after line 2, target shows 0. The program prints target: 0 and a number: true: Number turned the empty string into 0, and the check accepted it. After the edit, target shows NaN, and the program prints target: NaN and a number: false.
Expected output
target: 0
a number: true05 / Try it
Can you explain a fresh example?
Predict both output lines exactly, space by space, before you reveal the answer.
const room = "bedroom ";
const temp = 19.84;
console.log("room", room + "|");
console.log(JSON.stringify(room) + temp.toFixed(1).padStart(6)); What are the two lines, character by character?
Reveal the reasoning
room bedroom | and "bedroom " 19.8. console.log puts one space between "room" and its second value, room + "|". That value is bedroom, then the space at the end of room, then |. JSON.stringify shows room between quotes, so its last space is visible. toFixed(1) rounds 19.84 to "19.8", and padStart(6) adds two spaces in front, so the number takes 6 characters.
The next lesson, What is a value?, looks more closely at the numbers and text that programs print and read.
Practice tickets
- PRINT-101 · The washing machine shows 1:5 when 65 minutes are left → Free
- PRINT-102 · The smart lamp refuses every brightness typed on its keypad → Sign in · Pro
- PRINT-103 · Shopping list log lines look right but some items are never found → Sign in · Pro