Values, operators and control flow
The idea
A program is a long list of instructions that a very fast, very literal helper follows. To be useful, the helper needs three things: a place to keep what it knows, a way to make choices, and a way to repeat work without you retyping it.
Our picture for the first one is a set of cardboard boxes, each with a label written on it. The label is the name you choose, and the box holds a value. The rest of this lesson builds on that: the shape of what's in a box, how to calculate with it, how to make choices with it, and how to repeat work over it.
You'll write small files and run them with Node in your terminal. If you're on Windows, use your WSL2 (Ubuntu) terminal, the same one you set up earlier. The exact commands are in the Try it section at the end, so you can read first and type later.
Variables with let and const
A variable is a labeled box. You pick the label, put a value inside, and use the label whenever you want the value back. JavaScript has two ways to make one.
let makes a box you may unpack and refill later. const makes a box that gets taped shut after you pack it: the label stays on that box for good.
let score = 0;
const playerName = "Ada";
score = score + 10;
console.log(playerName, score);
Ada 10
console.log is a built-in command that prints whatever you give it to the terminal, with a space between the pieces. The first two lines make the boxes: score starts at 0, and playerName holds the text "Ada". The third line unpacks score, adds 10 and packs the answer back. The = sign here does not mean "equals". It means "put what's on the right into the box on the left."
Try to reopen a taped box and Node stops you:
const maxLives = 3;
maxLives = 5;
TypeError: Assignment to constant variable.
Node prints a few lines, and the ones starting with at point to where it was in your files. Ignore those for now. The line that matters has two parts: the word before the colon names the kind of mistake, and the sentence after it says what you did wrong. Here you tried to assign a new value to a const.
You also can't make two boxes with the same label:
let level = 1;
let level = 2;
SyntaxError: Identifier 'level' has already been declared
This one is caught before anything runs. If you want a new value in the same box, drop the word let and write level = 2;.
The habit to build: start with const. Switch to let only when you know the value will change. When you read a const later, you know it never moved, and that makes code easier to trust. You'll see an older keyword, var, in old code online. Skip it, let and const replace it.
Names are case-sensitive and can't start with a digit or contain spaces. The common style is camelCase: playerName, maxLives, totalScore.
Types, coercion and equality
Every value has a type, which is the shape of the box it comes in. A number box, a text box and a yes-or-no box are different shapes, and what you can do with a value depends on its shape. The word typeof tells you the shape:
console.log(typeof 42);
console.log(typeof "hello");
console.log(typeof true);
console.log(typeof undefined);
number
string
boolean
undefined
The shapes you'll use most:
- A number is a whole number or a decimal, like
7or2.5. They share one shape. - A string is text inside quotes:
"hello",'hello'or backticks. - A boolean is one of two values,
trueorfalse. - undefined is a box you made but never packed.
- null is a box you packed on purpose with "nothing here".
let winner;
const middleName = null;
console.log(winner);
console.log(middleName);
undefined
null
winner was made without a value, so JavaScript fills it with undefined. middleName was set to null by you, as a deliberate way of saying "this is empty".
Coercion means JavaScript quietly repacking a value into a different shape when you mix shapes. Sometimes that's helpful and sometimes it's a trap:
console.log("5" + 1);
console.log("5" - 1);
console.log("5" * "2");
console.log(Number("42") + 1);
console.log(Number("forty-two"));
console.log(Number(true));
51
4
10
43
NaN
1
Read them in order. With +, if either side is text, JavaScript glues the pieces together as text, so "5" + 1 becomes "51". The signs - and * only make sense for numbers, so JavaScript repacks both sides as numbers first. Number(...) is a built-in converter: you ask for a number box on purpose. Number("forty-two") can't work, so you get NaN, short for "Not a Number". It's the value JavaScript gives you when a calculation fails. And Number(true) is 1. There is a matching String(...) converter for going the other way, so String(99) gives "99".
Two values can look the same and still be different shapes, so JavaScript has two kinds of equality check:
console.log(5 === "5");
console.log(5 == "5");
console.log(null === undefined);
console.log(null == undefined);
console.log(1 === true);
console.log(1 == true);
false
true
false
true
false
true
=== is strict equality: the shape and the contents must both match. == is loose equality: it repacks the values first and then compares, which is where the surprises come from. Use === every time (and !== for "is not equal"). Loose equality has odd rules that few people remember.
NaN has its own oddity: it isn't equal to anything, not even itself.
const broken = Number("oops");
console.log(broken === NaN);
console.log(Number.isNaN(broken));
console.log(Number.isNaN("oops"));
false
true
false
broken === NaN is false even though broken is NaN. To check for it, use Number.isNaN(...), a ready-made check that answers true only when the value is exactly NaN. The third line is false because the text "oops" is a string. It is not a failed calculation.
Operators and template literals
Operators are the buttons on a calculator keypad: symbols that take values and give back a new one. Arithmetic comes first:
const price = 8;
const quantity = 3;
console.log(price * quantity);
console.log(7 / 2);
console.log(2 ** 5);
console.log(29 % 10);
console.log(2 + 3 * 4);
console.log((2 + 3) * 4);
24
3.5
32
9
14
20
* multiplies and / divides. ** is "to the power of", so 2 ** 5 is 2 times itself five times. % is remainder: what's left after dividing as many whole times as possible. 29 % 10 is 9, the last digit of 29. Multiplication happens before addition, just like in school maths, and parentheses go first.
A second family of buttons keeps a running total. These save you from writing coins = coins + 5:
let coins = 10;
coins += 5;
coins *= 2;
coins++;
console.log(coins);
31
+= adds to the box, *= multiplies it, and ++ adds 1. After those lines coins goes 10, 15, 30, 31. (-=, /= and -- work the same way.)
A third family compares and combines yes-or-no answers. The comparisons are <, >, <=, >=, === and !==. The combiners are && ("and"), || ("or") and ! ("not"):
const age = 15;
console.log(age >= 13 && age < 18);
console.log(age < 13 || age > 65);
console.log(!(age >= 13));
true
false
false
The first line is true because both halves are true. The second is false because neither half is. The third asks "is it not true that age is at least 13?" and gets false.
Putting values into sentences by gluing pieces with + gets messy fast. A template literal is a form letter with blanks: you write the sentence between backticks and put each blank as ${...}. JavaScript fills every blank with the value inside.
const item = "ticket";
const count = 3;
const cost = 12;
console.log(`${count} ${item}s cost ${count * cost} dollars`);
3 tickets cost 36 dollars
Whatever sits inside ${ } is worked out first, so ${count * cost} becomes 36. The backtick key is usually at the top left of the keyboard, under Escape. It is not the apostrophe. A template literal can also run across several lines, and the line break is kept:
const item = "ticket";
const note = `Dear ${item} holder,
see you soon.`;
console.log(note);
Dear ticket holder,
see you soon.
Truthy and falsy values
Picture a security gate that looks at every value and either waves it through or stops it. The gate doesn't need the value to be a boolean. It just asks, "does this one count as yes?" The values that count as yes are truthy, and the ones that get stopped are falsy.
The stop list is short. These are falsy: false, 0, the empty string "", null, undefined and NaN. Everything else is truthy. The built-in Boolean(...) converter shows the gate's verdict:
console.log(Boolean(0));
console.log(Boolean(""));
console.log(Boolean(NaN));
console.log(Boolean("0"));
console.log(Boolean("false"));
console.log(Boolean(-1));
false
false
false
true
true
true
The last three catch people out. The text "0" has a character in it, so it isn't empty. The text "false" is just letters, and -1 isn't 0. The gate only stops the exact values on its list.
The ! operator uses the same gate and flips the verdict:
const cartCount = 0;
console.log(!cartCount);
console.log(!"hello");
true
false
cartCount is 0, which is falsy, so !cartCount is true. The non-empty text "hello" is truthy, so !"hello" is false. In the next section you'll see where this gate matters most: the condition slot of an if, which accepts any value and asks the gate about it.
If, else and switch
A conditional is a railway switch. The train (your program) reaches a fork, the switch looks at a condition, and the train goes down exactly one track.
const temperature = 31;
if (temperature >= 30) {
console.log("Turn on the fan");
} else if (temperature >= 18) {
console.log("Leave things as they are");
} else {
console.log("Turn on the heater");
}
Turn on the fan
if checks its condition, the part in parentheses. If it's truthy, the track inside the curly braces runs and the rest is skipped. If not, the next else if is tried. The final else is the track for everything left over, and it needs no condition. The switch checks from the top and takes the first track that matches, so the order matters. Had we put >= 18 first, 31 would have taken that track.
Because the condition slot goes through the truthy gate, you can write:
const username = "";
if (username) {
console.log("Welcome back!");
} else {
console.log("Please type a name.");
}
Please type a name.
The empty string is falsy, so the else track runs.
When one value can be one of several exact choices, the switch statement is tidier than a long else if chain. It compares one value against each case using ===:
const cup = "medium";
let ounces;
switch (cup) {
case "small":
ounces = 8;
break;
case "medium":
ounces = 12;
break;
case "large":
ounces = 16;
break;
default:
ounces = 0;
}
console.log(ounces);
12
JavaScript finds the matching case and runs from there. break means "leave the switch now". default is the track for anything that matched nothing. Several cases can share one track by stacking them with no break between:
const day = "sun";
switch (day) {
case "sat":
case "sun":
console.log("weekend");
break;
default:
console.log("weekday");
}
weekend
Use switch when you're matching one value against exact options. Use if and else if when you're comparing ranges like >= 30.
Loops: for, while and for...of
A loop is a tireless assembly-line worker who repeats one task until a counter says stop. You describe the task once, and the worker does it as many times as needed.
The for loop is for when you know how many laps to run. Its header has three parts: where the counter starts, the condition for keeping going, and what to do to the counter after each lap.
for (let count = 3; count >= 1; count--) {
console.log(count);
}
console.log("Liftoff!");
3
2
1
Liftoff!
The counter starts at 3. Before each lap the worker checks count >= 1. After each lap count-- takes 1 off. When the counter reaches 0, the check fails and the worker moves on to the line after the loop. Loops and if work together naturally, and % is handy for "every third one":
for (let row = 1; row <= 6; row++) {
if (row % 3 === 0) {
console.log(`row ${row} is highlighted`);
}
}
row 3 is highlighted
row 6 is highlighted
Two keywords steer the worker from inside. continue skips the rest of this lap, and break stops the whole loop:
for (let n = 1; n <= 10; n++) {
if (n === 4) {
continue;
}
if (n === 7) {
break;
}
console.log(n);
}
1
2
3
5
6
The number 4 was skipped, and the loop ended at 7 before it could print.
Use while when you don't know how many laps are needed, only the condition to stop. The worker checks the condition before every lap:
let savings = 20;
let weeks = 0;
while (savings < 100) {
savings += 30;
weeks++;
}
console.log(`${weeks} weeks, ${savings} saved`);
3 weeks, 110 saved
The savings go 20, 50, 80, 110, and at 110 the check savings < 100 fails. If nothing inside the loop ever makes its condition false, the worker never stops. That's an infinite loop. If it happens, press Ctrl+C in the terminal, which stops the running program.
Use for...of when you have a list and want to handle each item in turn. A list is items written in square brackets, separated by commas. The worker takes each item off the line, one at a time:
const bands = ["Muse", "Queen", "Blur"];
for (const band of bands) {
console.log(`Now playing: ${band}`);
}
Now playing: Muse
Now playing: Queen
Now playing: Blur
Each lap, band is a fresh const box holding the next item, so you can't accidentally change it. The same loop reads text one character at a time:
for (const letter of "hey") {
console.log(letter);
}
h
e
y
A quick way to choose: for when you can count the laps, while when you only know when to stop, and for...of when you have a list to walk through.
Common mistakes
- Writing
=when you meant===. A single=puts a value into a box. It never compares. Inside anif,if (score = 5)quietly overwritesscoreand then always takes the first track. - Trusting
==. It repacks values before comparing, so5 == "5"istrue. Use===and!==. - Adding a number to text.
"5" + 1is"51". Text from a form or a file is always a string, so convert it withNumber(...)before doing sums. - Checking for NaN with
=== NaN. That's alwaysfalse. UseNumber.isNaN(...). - Reaching for
letout of habit. If the value never changes, useconst, and Node will catch you if you try to change it. - Thinking
"0"and"false"are falsy. They're text with characters in them, so they pass the gate. - Forgetting
breakin aswitch. The program doesn't stop at the matching case. It carries on into the next one:
const light = "red";
switch (light) {
case "red":
console.log("stop");
case "green":
console.log("go");
}
stop
go
- Off-by-one in a loop.
row < 6runs for rows 1 to 5, whilerow <= 6includes 6. Say out loud which numbers you want, then pick the symbol. - Forgetting to change the counter in a
whileloop. The condition never turns false and the program never stops. PressCtrl+Cand add the missing line. - Using an apostrophe instead of a backtick for a template literal. With
'${name}'you get the characters${name}printed as they are. No blank is filled.
Try it
Set up a practice folder and a file, then add to the same file as you go. Run it after each step to see what changed.
mkdir -p ~/js-basics
cd ~/js-basics
code values.js
node values.js
mkdir -p makes the folder, cd moves into it, code values.js opens a new file in VS Code, and node values.js runs the file with Node and shows whatever your console.log lines print. Run the last command again after every step.
- Make boxes. In
values.js, writeconst playerName = "Ada";,let score = 0;,score = score + 10;andconsole.log(playerName, score);. You should seeAda 10. Then add the lineplayerName = "Grace";right above theconsole.logand run it. Read theTypeError: Assignment to constant variable.message out loud, then delete that line. - Look at shapes. Add
console.log(typeof 3.5);,console.log(typeof "3.5");,console.log(typeof false);andlet spare;followed byconsole.log(spare);. You should seenumber,string,booleanandundefined. Then addconst nickname = null;andconsole.log(nickname);and notice it printsnull. - Predict, then check. Write down what you think each of these prints, then add them and compare:
console.log("4" + 1);,console.log("4" * 3);,console.log(4 === "4");,console.log(4 == "4");,console.log(Number("4x"));andconsole.log(Number.isNaN(Number("4x")));. Pay attention to the ones you got wrong. - Calculate and fill in a form letter. Add
const nights = 3;andconst rate = 90;, then print one sentence with a template literal that says how many nights, the rate, and the total cost, with the total worked out inside the${ }. Also print2 + 3 * 4,(2 + 3) * 4and47 % 10and explain each answer to yourself. - Test the gate. Print
Boolean(...)for each of0,"","0","hello",null,undefined,-5andNaN. Before running, write down which ones you expect to befalse. Then check. - Make choices. Add
const hour = 14;and write an if, else if, else chain that prints "morning" before 12, "afternoon" before 18, and "evening" otherwise. Changehourto 8 and 20 to visit every track. Then addconst drink = "tea";and aswitchwith cases for"tea"and"coffee"that print a different message each, plus adefault. Changedrinkto each case and to something else. - Repeat work. Write a
forloop that counts down from 5 to 1, then prints "Done". Write awhileloop that startslet seats = 1;and doubles it withseats *= 2until it reaches at least 50, then prints how many seats you ended with. Finally, usefor...ofto print each item of["red", "green", "blue"], and then each letter of your own first name.