2.1 Variables: Giving a Value a Name
Before you start, set up a new project folder named day02 exactly as you set up day01 in 1.2: open the folder in VS Code, run npm init -y and npm install --save-dev typescript in its terminal, and type the same tsconfig.json. Every program today goes in that folder.
On Day 1, every number you used was typed straight into an expression, like 30 - 12. That works once. But imagine a program that uses a ticket price in five places: if the price changes, you must find and edit all five, and missing one gives a wrong answer. Programs need a way to work a value out once, remember it, and refer to it by name.
A variable is a name that holds a value. You create one with a variable declaration. The simplest kind starts with the word let:
let name = expression;
| Piece | What it is |
|---|---|
let |
“Make a new variable.” It is a keyword: a word with a fixed meaning in the language |
name |
The name you choose for the variable |
= |
The assignment operator: store the value on the right in the name on the left |
expression |
Any expression, worked out first |
; |
The end of the statement, as on Day 1 |
The program runs a declaration in two steps. First it works out the expression on the right. Then it stores that value under the name on the left. From then on, wherever you write the name, the program uses the value stored there. Storing a value in a variable is called assigning it. The = sign does not mean “is equal to” as it does in math; read let price = 4; as “let price be 4” or “price gets 4”.
Think of a variable as a labeled box: the name is the label, and the box holds one value at a time.
Worked example: A ticket total
Watch what each name holds after each line.
let ticketPrice = 12;
let ticketsSold = 35;
let total = ticketPrice * ticketsSold;
console.log("Tickets sold:", ticketsSold);
console.log("Total:", total);Tickets sold: 35
Total: 420| Line | ticketPrice |
ticketsSold |
total |
|---|---|---|---|
| 1 | 12 | ||
| 2 | 12 | 35 | |
| 3 | 12 | 35 | 420 |
| 4 | 12 | 35 | 420 |
| 5 | 12 | 35 | 420 |
Lines 1 and 2 each store a number under a name. Line 3 looks up both names, works out 12 * 35, and stores 420 under total. Lines 4 and 5 look up a name and show its value; they change nothing.
Line 3 never mentions 12 or 35; it uses the names, and the program looks up their values. If the price changes, you edit line 1 and every line after it uses the new price.
Now vary one thing: assign a new value to a variable that already exists. You write the name, =, and the new value, without let, because the variable is already made. The old value is thrown away and the new one takes its place. This lets you update a variable, even using its own old value on the right side.
let score = 10;
console.log("Start:", score);
score = score + 5;
console.log("After bonus:", score);
score = 0;
console.log("After reset:", score);Start: 10
After bonus: 15
After reset: 0
| Line | Right side worked out | score after the line |
|---|---|---|
| 1 | 10 |
10 |
| 3 | score + 5 is 10 + 5 |
15 |
| 5 | 0 |
0 |
Line 3, score = score + 5;, would be nonsense as a math equation, but as an assignment it makes sense: the right side is worked out first, using the current value 10, and only then is the result, 15, stored back in score. The let appears only once, on line 1, where the variable is made.
Key idea
let name = expression; makes a variable: the right side is worked out first, then stored under the name. Later,
name = expression; (no let) replaces the old value with a new one.
Watch out
Using a variable before the line that makes it. A program runs top to bottom, so a variable exists only from its declaration onward:
console.log("Total:", total);
let total = 5 * 3;use-before-declare.ts:1:23 - error TS2448: Block-scoped variable 'total' used before its declaration.
use-before-declare.ts:1:23 - error TS2454: Variable 'total' is used before being assigned.The type checker reports two messages about the same spot, line 1, column 23, where total is used. Both say the same thing in different words: line 1 uses total, but the declaration is on line 2. (“Block-scoped” describes variables made with let and const; you can ignore the word for now.) Move the declaration above the console.log. If you typed this file, fix or delete it now.
Pause and try
Question 1 · Tracing
What does this program show? Make a table of apples and pears after each line.
let apples = 6;
apples = apples + 4;
let pears = apples - 3;
apples = 2;
console.log(apples, pears);Check your reasoning
2 7| Line | Right side worked out | apples |
pears |
|---|---|---|---|
| 1 | 6 |
6 | |
| 2 | apples + 4 is 6 + 4 |
10 | |
| 3 | apples - 3 is 10 - 3 |
10 | 7 |
| 4 | 2 |
2 | 7 |
Line 4 changes apples to 2, but pears keeps 7. Line 3 used the value apples had then; changing apples later does not go back and change pears. If you wrote 2 -1, you recalculated pears from the new apples.
Reread 2.1 Variables: Giving a Value a Name if your output differed.
Question 2 · Debugging
This program should show the old price, then change it to 5 and show the new price. npx tsc reports the messages below. Explain the mistake and write the fix.
let price = 4;
console.log("Old price:", price);
let price = 5;
console.log("New price:", price);price-change.ts:1:5 - error TS2451: Cannot redeclare block-scoped variable 'price'.
price-change.ts:3:5 - error TS2451: Cannot redeclare block-scoped variable 'price'.Check your reasoning
Line 3 starts with let, which asks for a new variable named price, but one already exists from line 1. Redeclare means “declare again,” and a program cannot have two variables with the same name, so the checker points at both declarations (column 5 is where price starts on each line). To change the existing variable, assign without let:
let price = 4;
console.log("Old price:", price);
price = 5;
console.log("New price:", price);Old price: 4
New price: 5Reread 2.1 Variables: Giving a Value a Name if you missed it.
2.2 Constants: Names That Never Change
Look back at ticket-total.ts in 2.1. None of its three variables ever changed after it was made. Most values in a program are like that: a price, a name, a total worked out once. If such a value changed by accident, the program would quietly give wrong answers.
TypeScript has a second kind of declaration for values that must stay the same. It starts with const (short for “constant”) instead of let:
const name = expression;
A variable declared with const is a constant: it gets its value once, when it is made, and can never be assigned a new one. A variable declared with let can be assigned again as often as you like. You met const once already, in Day 1’s preview const muffinsLeft: number = 12; (1.8).
| Declaration | Can it be assigned again? | Use it for |
|---|---|---|
const |
No | Values that never change (most) |
let |
Yes | Values that change, like a score |
Worked example: Constants and one variable that changes
Here is the concert program again, with one value that does change: the number of seats left.
const ticketPrice = 12;
const ticketsSold = 35;
const total = ticketPrice * ticketsSold;
let seatsLeft = 40;
seatsLeft = seatsLeft - ticketsSold;
console.log("Total:", total);
console.log("Seats left:", seatsLeft);Total: 420
Seats left: 5The price, the number sold, and the total never change, so they are const. seatsLeft starts at 40 and is updated on line 5 to 40 - 35, which is 5, so it must be let.
Now vary it: try to assign a new value to a constant.
const busSeats = 40;
console.log("Seats:", busSeats);
busSeats = 45;const-reassign.ts:3:1 - error TS2588: Cannot assign to 'busSeats' because it is a constant.
The checker points at line 3, column 1, where the assignment starts, and the message says exactly why: busSeats was made with const. Line 2 is fine; reading a constant is always allowed. Only assigning to it is not. If the number of seats really does change, declare it with let; if it should not change, delete line 3. Fix or delete the file before you go on.
Why is const the default choice? Because it is a promise the type checker enforces. A reader who sees const knows the value is the same on every line after it, without searching the program for changes. And if you assign to it by mistake, you find out from npx tsc before the program ever runs. Good practice is to write const first and change it to let only when the value really needs to change. (Older JavaScript code uses a third keyword, var, which you may see online. This course never uses it.)
Key idea
const makes a variable that can never be assigned again; let makes one that can. Use const unless the value must
change, so the type checker catches accidental changes.
Watch out
Using let for everything. A program full of let runs fine, and no error appears. But you lose the protection: if you change a value by accident, nothing warns you. Ask of each variable, “does this ever change?” Only the ones that do get let.
Pause and try
Question 3 · Multiple choice
Which line does npx tsc reject?
const lunchPrice = 6;
let lunchesSold = 0;
lunchesSold = lunchesSold + 3;
const total = lunchPrice * lunchesSold;
lunchPrice = 7;- Line 3, because it uses
lunchesSoldon both sides of= - Line 4, because it uses two variables at once
- Line 5, because
lunchPriceis a constant - None; the program type-checks
Check your reasoning
C. lunchPrice was declared with const, so it can never be assigned again. The checker reports lunch-sales.ts:5:1 - error TS2588: Cannot assign to 'lunchPrice' because it is a constant.
- A is wrong:
lunchesSoldwas declared withlet, so updating it from its own old value (0 + 3) is allowed. - B is wrong: reading any number of variables, constant or not, is always allowed. Line 4 makes a new constant,
total, which is fine. - D is wrong: line 5 assigns to a constant.
Reread 2.2 Constants: Names That Never Change if you chose differently.
2.3 Naming Rules and camelCase
You choose every variable name, so two questions follow: which names does TypeScript accept, and which names make a program easy to read?
A name you choose is called an identifier. The rules are short:
- An identifier may contain letters, digits, the underscore
_, and the dollar sign$, and nothing else: no spaces, hyphens, or other symbols. - It cannot start with a digit.
- Capital and small letters count as different (names are case-sensitive, as Day 1 showed), so
scoreandScoreare two different names. - It cannot be a reserved word, one of the keywords the language keeps for its own grammar, such as
let,const,class,true,false, andnull.
Why these rules? The type checker has to tell names apart from everything else on a line. A name starting with a digit would look like a number, a space would make one name look like two, and a hyphen looks like a minus sign.
On top of the rules there is a convention, a habit every TypeScript programmer follows even though the checker does not enforce it. Variable names use camelCase: the first word all lowercase, and every later word starting with a capital letter, with no spaces or underscores, like ticketPrice or maxSpeedKmh. The capitals in the middle stick up like the humps of a camel. Choose names that say what the value means: ticketsSold tells a reader far more than t or x.
Worked example: Names that work
const playerName = "Ana";
const player2Score = 18;
const maxSpeedKmh = 120;
console.log(playerName, player2Score, maxSpeedKmh);Ana 18 120All three names follow the rules and the convention. player2Score contains a digit, which is fine because it does not start with one. maxSpeedKmh adds the unit, a good habit that prevents mix-ups between kilometres and miles.
Now vary the names so they break the rules. Each of these is a syntax error, so npx tsc reports it before anything runs:
let total cost = 5;name-with-space.ts:1:11 - error TS1005: ',' expected.
let 2ndPlace = "Lee";name-starts-with-digit.ts:1:1 - error TS1440: Variable declaration not allowed at this location.
name-starts-with-digit.ts:1:7 - error TS1005: ';' expected.
name-starts-with-digit.ts:1:7 - error TS1351: An identifier or keyword cannot immediately follow a numeric literal.
const class = "7B";name-is-keyword.ts:1:7 - error TS1389: 'class' is not allowed as a variable declaration name.
name-is-keyword.ts:1:13 - error TS1005: '{' expected.
In the first, the checker reads total as the whole name and then finds a second word, cost, where it expected the declaration to go on (column 11 is where cost starts). In the second, it reads 2 as the start of a number and then finds letters glued to it; a numeric literal is a number typed directly into the code, and no number can contain letters. In the third, class is a reserved word, and the first message says so plainly. As on Day 1, read the first message of each group; the others follow from it. The fixes are totalCost, secondPlace, and a name like className. If you typed these files, delete them now.
| Name | Allowed? | Why |
|---|---|---|
totalCost |
Yes | Letters only; good camelCase |
player2 |
Yes | A digit is fine after the first character |
2ndPlace |
No | Starts with a digit |
total cost |
No | Contains a space |
total-cost |
No | The hyphen is read as minus |
class |
No | A reserved word |
total_cost |
Yes | Allowed, but not the convention for variables |
Key idea
An identifier uses letters, digits, _, and $, does not start with a digit, and is not a reserved word. By
convention, variable names are descriptive camelCase, like ticketsSold.
Watch out
Two names that differ only in capitals. Because names are case-sensitive, Score is a completely different name from score. Typing it by mistake is caught, because TypeScript only lets you assign to a variable that has been declared:
let score = 10;
Score = score + 5;
console.log(score);wrong-capitals.ts:2:1 - error TS2552: Cannot find name 'Score'. Did you mean 'score'?This is the Day 1 message for an unknown name, with a suggestion because Score is close to score. Sticking to camelCase (always a small first letter) makes this mistake rare. Fix the capital before you go on.
Pause and try
Question 4 · Multiple choice
Which is a valid variable name that also follows the camelCase convention?
final scorefinalScorefinal_score2finalScore
Check your reasoning
B. finalScore uses only letters, starts with a small letter, and marks the second word with a capital.
- A is wrong: the space makes the checker see two words, a syntax error (TS1005).
- C is wrong: it is allowed, but underscores between words are not the camelCase convention.
- D is wrong: a name cannot start with a digit.
Reread 2.3 Naming Rules and camelCase if you chose differently.
2.4 Numbers, Strings, and Booleans
On Day 1 you learned that every value has a type, the kind of value it is, and you met two: number and string (1.7). The type decides what you can do with a value: you can multiply numbers, but multiplying two pieces of text makes no sense. Today you meet three everyday types, and in 2.7 two more.
numberis the type of every number: whole numbers like42,0, and-7, and decimals like3.5and0.45. Many languages have separate types for whole numbers and decimals; JavaScript has just one. That is why Day 1’s8 / 2showed4and7 / 2showed3.5: both are simply numbers.stringis the type of text in quotes, like"Hatchet".booleanis the type of a value that is eithertrueorfalse, and nothing else. Those two words are written in small letters and without quotes. A boolean answers a yes-or-no question: is the shop open? Is the book a hardcover? On Day 4 you will make booleans by comparing values; today you store and show them.
To find out a value’s type while the program runs, put the keyword typeof in front of it. typeof gives back the type’s name as a string: typeof 42 is "number". This is JavaScript speaking: typeof runs inside node and reports what the value is at that moment.
Worked example: The types in a book record
const pages = 320;
const weightKg = 0.45;
const title = "Hatchet";
const isHardcover = false;
console.log(pages, typeof pages);
console.log(weightKg, typeof weightKg);
console.log(title, typeof title);
console.log(isHardcover, typeof isHardcover);320 number
0.45 number
Hatchet string
false boolean| Name | Value | Type | Meaning |
|---|---|---|---|
pages |
320 |
number |
A count |
weightKg |
0.45 |
number |
A measurement |
title |
"Hatchet" |
string |
Text |
isHardcover |
false |
boolean |
A yes-or-no fact |
typeof works on a variable too: it reports the type of the value stored there. A boolean’s name often starts with is or has, so that it reads like a yes-or-no question.
Now vary the quotes. console.log shows a string without its quotes, so a number and a string of digits look the same in the terminal. typeof tells them apart:
console.log(42, typeof 42);
console.log("42", typeof "42");
console.log(true, typeof true);
console.log("true", typeof "true");
console.log(typeof (6 * 7));42 number
42 string
true boolean
true string
number
Lines 1 and 2 both show 42, and lines 3 and 4 both show true, but the types differ: the quotes make "42" and "true" strings, pieces of text that happen to contain digits or a word. The last line shows that typeof works on an expression too: 6 * 7 is worked out to 42, a number. The parentheses matter there: without them, typeof would apply only to the 6.
Key idea
number covers whole numbers and decimals alike, string is text in quotes, and boolean is true or false
(small letters, no quotes). typeof value gives the type’s name while the program runs.
Watch out
Writing True with a capital. Some other languages spell the boolean values with a capital letter, and students carry the habit over:
const isMember = True;
console.log("Member:", isMember);true-capital.ts:1:18 - error TS2304: Cannot find name 'True'.With a capital, True is not the boolean value; it is just an unknown name, so you get Day 1’s TS2304. Write true. And do not put quotes around it: "true" is a string, as typeof-quotes.ts showed. Fix or delete the file before you go on.
Pause and try
Question 5 · Multiple choice
Which value has the type boolean?
"false"falseFalse0
Check your reasoning
B. false is one of the two boolean values, written in small letters with no quotes.
- A is wrong: the quotes make it a string, five letters of text.
- C is wrong: with a capital F,
Falseis an unknown name, and the checker reports TS2304. - D is wrong:
0is a number.
Reread 2.4 Numbers, Strings, and Booleans if you chose differently.
2.5 Type Annotations and Inference
typeof tells you a type while the program runs. But the type checker works before anything runs, so it needs to know each variable’s type from the code alone. There are two ways it finds out.
The first is that you tell it. A type annotation is a colon and a type name written after a variable’s name: let visitors: number = 0; reads “visitors is a variable of type number, starting at 0.” You saw one on Day 1 in const muffinsLeft: number = 12;.
let name: type = expression;
| Piece | What it is |
|---|---|
: type |
The annotation: the type this variable must always hold |
= expression |
The starting value, which must have that type |
The type names in annotations are the ones typeof reports: number, string, boolean. An annotation is TypeScript only; when Node runs the file, it strips the annotation away, as Day 1 explained.
Worked example: Annotations on every variable
let visitors: number = 0;
const city: string = "Lima";
const isOpen: boolean = true;
visitors = visitors + 25;
console.log(city, isOpen, visitors);Lima true 25Each variable is labeled with its type, and each starting value has that type, so npx tsc reports no errors for this file. Line 4 adds 25 to visitors; a number plus a number is a number, so the checker accepts storing it back.
Now vary it: take every annotation away.
let visitors = 0;
const city = "Lima";
const isOpen = true;
visitors = visitors + 25;
console.log(city, isOpen, visitors);Lima true 25
The output is the same, and npx tsc still reports no errors for it. The checker did not need the annotations, because it can see the starting values: 0 is a number, so visitors must be a number. Working out a type from the code without being told is called type inference. In VS Code, hold the mouse pointer over visitors and a small box shows let visitors: number: the type the checker inferred. (Over a const, the box shows the value itself, such as const city: "Lima", because a constant can never hold anything else; Day 9 explains that kind of type.)
So when should you write an annotation? When the checker cannot see a starting value to infer from (2.7 shows that case), or when you want to state your intention so the checker can hold you to it. When a variable gets an obvious starting value, most programmers let inference do the work, and this course does the same from here on.
Key idea
A type annotation (let visitors: number = 0;) tells the checker a variable’s type. Without one, the checker infers
the type from the starting value. Either way, the variable has a type before the program runs.
Watch out
An annotation that does not match the value. An annotation is a rule, and the starting value must follow it:
const isOpen: boolean = "true";
console.log("Open:", isOpen);annotation-mismatch.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'boolean'.Read it as Day 1 taught: Type what you gave is not assignable to type what was required. "true" is in quotes, so it is a string, but the annotation requires a boolean. Remove the quotes. Fix or delete the file before you go on.
Pause and try
Question 6 · Short answer
For each variable, name the type the checker gives it, and say whether it came from an annotation or from inference. Which annotation could you remove without changing anything?
let lives = 3;
const nickname = "Ace";
let isPaused = false;
let speed: number = 2.5;
console.log(lives, nickname, isPaused, speed);Check your reasoning
Model answer:
| Variable | Type | Where it came from |
|---|---|---|
lives |
number |
Inferred from 3 |
nickname |
string |
Inferred from "Ace" (the hover shows "Ace") |
isPaused |
boolean |
Inferred from false |
speed |
number |
The annotation : number |
The annotation on speed can be removed: 2.5 is a number, so the checker would infer number anyway. The program shows 3 Ace false 2.5 either way.
A complete answer must:
- give
number,string,boolean, andnumber, and - say that
speed’s annotation is the one that can go, because inference gives the same type.
Reread 2.5 Type Annotations and Inference if your answer differed.
2.6 Static Typing: A Variable Keeps Its Type
In 2.1 you assigned a new number to score. Could you assign a string to it instead? In plain JavaScript, yes: a JavaScript variable holds whatever you put in it, and a number can be replaced by text without complaint. That freedom causes bugs, because later lines still expect the number. TypeScript closes that door.
In TypeScript, a variable’s type is fixed when it is declared, by its annotation or by inference, and it never changes. Every later assignment must give a value of that type. This is called static typing: “static” means fixed, settled before the program runs. The type checker reads every assignment and rejects any that does not fit.
Worked example: A count that must stay a number
let count: number = 0;
count = "five";
console.log(count);count-five.ts:2:1 - error TS2322: Type 'string' is not assignable to type 'number'.Line 1 declares count with the type number. Line 2 tries to store the string "five" in it. The checker points at line 2, column 1, where the assignment starts: a string is not allowed where a number is required. let lets you change the value, but never the type.
Now vary it: leave out the annotation, and add a line that stores a different number.
let count = 0;
count = 5;
count = "five";
console.log(count);count-five-inferred.ts:3:1 - error TS2322: Type 'string' is not assignable to type 'number'.
The same message, now for line 3. The checker inferred number from the starting value 0, and an inferred type is just as fixed as an annotated one. Line 2 is accepted: 5 is a different value, but the same type. Only line 3 breaks the rule.
| Line | Value’s type | Variable’s type | Accepted? |
|---|---|---|---|
let count = 0; |
number |
number (inferred) |
Yes, it is declared here |
count = 5; |
number |
number |
Yes |
count = 2.5; |
number |
number |
Yes, decimals are numbers too |
count = "five"; |
string |
number |
No, TS2322 |
If you typed these two files, fix or delete them now. The fix depends on what you meant: if the count is a number, store 5; if you need the word, make a separate const countText = "five";.
Key idea
A variable’s type is set when it is declared and never changes. The type checker rejects any assignment of a different
type, such as count = "five" for a number, before the program runs.
Watch out
Changing the annotation to make the error go away. When TS2322 appears, it is tempting to edit the type until the message stops. But the message is usually right: the variable was meant to hold one kind of value, and some line is trying to put another kind in. Ask which line is wrong, the declaration or the assignment, and fix that one.
Pause and try
Question 7 · Tracing
Which line does npx tsc reject? Write the full message it prints.
let temperature = 21;
temperature = 22.5;
temperature = temperature - 4;
temperature = "cold";
console.log(temperature);Check your reasoning
thermostat.ts:4:1 - error TS2322: Type 'string' is not assignable to type 'number'.| Line | What is assigned | Its type | Accepted? |
|---|---|---|---|
| 1 | 21 |
number |
Declares temperature as number |
| 2 | 22.5 |
number |
Yes |
| 3 | temperature - 4 |
number |
Yes, a number minus a number |
| 4 | "cold" |
string |
No |
Line 2 is accepted because whole numbers and decimals share one type. Only line 4 tries to change the type. Nothing runs, so line 5 shows nothing.
Reread 2.6 Static Typing: A Variable Keeps Its Type if you picked a different line.
2.7 Null and Undefined
Some values are not known yet. A player with no middle name has none to store; a game that has not been scheduled has no date. JavaScript has two values that mean “nothing here,” and TypeScript gives each one a type.
nullmeans “deliberately empty”: the program knows there is no value. It is written in small letters, without quotes.undefinedmeans “no value has been given yet.” JavaScript gives a variable this value when nothing has been assigned to it.
Each is the only value of its own type: the value null has the type null, and the value undefined has the type undefined.
Worked example: Two kinds of nothing
const middleName = null;
const nextGame = undefined;
console.log(middleName, nextGame);
console.log(typeof middleName, typeof nextGame);null undefined
object undefinedLine 3 shows both values as words. Line 4 holds a surprise: typeof null is "object", not "null". That is a mistake from the very first version of JavaScript, and it can never be fixed, because changing it would break old websites that depend on it. Remember it as a quirk: typeof cannot tell you that a value is null. (Day 4 shows how to check for null directly.)
Now vary it: declare a variable with an annotation but no starting value. That is allowed with let, and it is the case 2.5 promised, where an annotation is needed because there is no value to infer from. Until you assign something, the variable holds undefined, and strict mode will not let you read it:
let finalScore: number;
console.log("Final score:", finalScore);unassigned.ts:2:29 - error TS2454: Variable 'finalScore' is used before being assigned.
The checker points at line 2, column 29, where finalScore is read. Nothing has been stored in it, so showing it would only show undefined, which is almost certainly a bug. Assign a value first and the checker is satisfied:
let finalScore: number;
finalScore = 88;
console.log("Final score:", finalScore);Final score: 88
Strict mode, the "strict": true setting from Day 1, also keeps null and undefined out of other types. A number variable must hold a number, and “nothing” is not a number:
let booksOut: number = null;
console.log("Books out:", booksOut);null-count.ts:1:5 - error TS2322: Type 'null' is not assignable to type 'number'.
This rule protects you: whenever a variable’s type is number, you can be sure it holds an actual number, so arithmetic on it will not go wrong. A variable that is sometimes a number and sometimes null needs a type that says so; that is a preview of Day 9, which teaches how to write one. Fix or delete unassigned.ts and null-count.ts before you go on.
Key idea
null means deliberately empty and undefined means not assigned yet; each is the only value of its own type. Strict
mode will not let you read a variable before it is assigned, or store null or undefined in a number or string.
Watch out
Trusting typeof for null. typeof null is "object", so a program that uses typeof to look for null never finds it. This is one of the few places where typeof misleads you; for every other value today, it reports the type you expect.
Pause and try
Question 8 · Multiple choice
Which line does npx tsc accept?
let winner: number = null;const nickname = null;let goals: number;followed byconsole.log(goals);const total: number = undefined;
Check your reasoning
B. With no annotation, the checker infers the type null from the value null, so the value fits its own type.
- A is wrong: in strict mode,
nullis not a number, so the checker reports TS2322,Type 'null' is not assignable to type 'number'. - C is wrong:
goalsis read before anything is assigned to it, so the checker reports TS2454,Variable 'goals' is used before being assigned. - D is wrong:
undefinedis not a number either, so this is TS2322 again, with'undefined'in place of'null'.
Reread 2.7 Null and Undefined if you chose differently.
2.8 Remainders, Powers, and Rounding
A movie is 135 minutes long. How many hours and minutes is that? 135 / 60 gives 2.25, which is true but not what you want. You want “2 hours and 15 minutes”: a whole number of hours, and the minutes left over. Day 1 gave you +, -, *, and /. Today adds two more operators and two rounding tools.
- The remainder operator
%gives what is left over after dividing as many whole times as possible.135 % 60is 15, because two 60s make 120, and 135 - 120 is 15. The%sign here has nothing to do with percentages. - The exponent operator
**raises a number to a power.2 ** 10means 2 multiplied by itself 10 times, which is 1024. Math.floor(x)roundsxdown to a whole number.Math.floor(2.25)is 2. (“Floor” means the whole number at or below, like the floor below you.)Math.round(x)roundsxto the nearest whole number.Math.round(2.25)is 2, andMath.round(2.75)is 3.
Math is built in, like console, and holds many math tools. Read Math.floor as Day 1 taught you to read console.log: “Math’s floor function.” You call it with the number in parentheses, and it gives back the rounded number, which you can store, show, or use in another expression.
Worked example: Minutes to hours and minutes
const totalMinutes = 135;
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
console.log(totalMinutes, "minutes is", hours, "hours and", minutes, "minutes");135 minutes is 2 hours and 15 minutes| Line | Expression | Worked out | Value |
|---|---|---|---|
| 2 | Math.floor(totalMinutes / 60) |
135 / 60 is 2.25, rounded down | 2 |
| 3 | totalMinutes % 60 |
135 - 2 × 60 = 135 - 120 | 15 |
Line 4 shows six arguments, separated by spaces. Check the answer: 2 whole hours of 60 minutes plus 15 left over is 2 × 60 + 15 = 135, the number you started with. That check always works for positive numbers, because Math.floor(a / b) counts the whole groups and a % b counts what is left. Change totalMinutes to 200 and run it again: you get 3 hours and 20 minutes.
Now vary the numbers, to see what the rules really depend on:
console.log(8 % 2, 7 % 2);
console.log(-7 % 2);
console.log(2 ** 10, 9 ** 0.5);
console.log(Math.floor(3.99), Math.floor(-3.5));
console.log(Math.round(2.4), Math.round(2.5), Math.round(-2.5));0 1
-1
1024 3
3 -4
2 3 -2
% 2gives 0 for every even number and 1 for every odd one, so the remainder tells you whether a number is even.-7 % 2is-1: the remainder takes the sign of the number on the left.9 ** 0.5raises 9 to the power one half, which is the square root of 9:3.Math.floor(3.99)is 3, not 4: it always rounds down, however close the next number is. AndMath.floor(-3.5)is-4, because “down” means toward the lower number, and for negatives that is -4, not -3.Math.round(2.4)is 2, and a number exactly halfway rounds up to the larger number:Math.round(2.5)is 3, andMath.round(-2.5)is -2 (because -2 is larger than -3).
Key idea
% gives the remainder and ** raises to a power. Math.floor rounds down and Math.round rounds to the nearest
whole number, halves going up. Math.floor(a / b) whole groups and a % b left over rebuild a.
Watch out
Expecting an error when dividing by zero. Sharing 12 pizza slices among 0 people has no sensible answer, but neither the type checker nor Node reports an error:
const pizzaSlices = 12;
const people = 0;
console.log("Each person gets", pizzaSlices / people);
console.log("Left over:", pizzaSlices % people);Each person gets Infinity
Left over: NaNThe types fit (numbers divided by numbers), so npx tsc reports no errors for this file. And JavaScript does not stop the program: / by zero gives the special number Infinity, and % by zero gives NaN, a value you will meet in 2.10. Both are numbers, so the program runs on with a nonsense result. When a result shows Infinity or NaN, look for a division by zero.
Pause and try
Question 9 · Tracing
Predict exactly what this program shows.
console.log(17 % 5);
console.log(Math.floor(17 / 5));
console.log(17 / 5);
console.log(2 ** 5);
console.log(Math.round(17 / 5));Check your reasoning
2
3
3.4
32
317 % 5: three 5s make 15, and 17 - 15 leaves 2.Math.floor(17 / 5): 17 / 5 is 3.4, rounded down to 3.17 / 5: ordinary division, 3.4.2 ** 5: 2 × 2 × 2 × 2 × 2 is 32.Math.round(17 / 5): 3.4 is nearer to 3 than to 4, so 3.
Reread 2.8 Remainders, Powers, and Rounding if any line differed.
2.9 Operator Precedence
With six arithmetic operators, an expression like 10 + 3 ** 2 * 2 - 8 % 3 needs a clear rule for what happens first. Day 1 gave part of it: multiplication and division before addition and subtraction, parentheses first. Here is the full rule for today’s operators.
Operator precedence is the order in which the program applies operators. From first to last:
| Order | Operators | Notes |
|---|---|---|
| 1 | ( ) |
Parentheses, innermost first |
| 2 | ** |
Power; groups from the right |
| 3 | * / % |
Left to right |
| 4 | + - |
Left to right |
Operators on the same row are applied left to right, in the order they appear, with one exception: a chain of ** is worked out from the right. A function call like Math.floor(...) works out everything inside its parentheses first, just like a pair of parentheses.
Worked example: Evaluating step by step
Work out 10 + 3 ** 2 * 2 - 8 % 3 the way the program does, one operator at a time:
| Step | Rule used | Expression now |
|---|---|---|
| 0 | Start | 10 + 3 ** 2 * 2 - 8 % 3 |
| 1 | ** first: 3 ** 2 is 9 |
10 + 9 * 2 - 8 % 3 |
| 2 | * next, leftmost: 9 * 2 is 18 |
10 + 18 - 8 % 3 |
| 3 | Then %: 8 % 3 is 2 |
10 + 18 - 2 |
| 4 | + and - left to right: 28 - 2 |
26 |
console.log(10 + 3 ** 2 * 2 - 8 % 3);
console.log(100 / 10 / 2);
console.log(2 ** 3 ** 2);
console.log((2 ** 3) ** 2);26
5
512
64Line 1 agrees: 26. The next lines show the same-row rules. 100 / 10 / 2 goes left to right: 100 / 10 is 10, then 10 / 2 is 5. 2 ** 3 ** 2 goes right to left: 3 ** 2 is 9 first, then 2 ** 9 is 512. Parentheses change it: (2 ** 3) ** 2 is 8 ** 2, which is 64.
Now vary one thing in a real calculation: the parentheses. To average two test scores, you add them and divide by 2.
const scoreA = 88;
const scoreB = 94;
console.log(scoreA + scoreB / 2);
console.log((scoreA + scoreB) / 2);135
91
Without parentheses, / happens before +, so line 3 divides only 94 by 2 (47) and adds 88, giving 135, which is not an average. With parentheses, the addition happens first, and the average is 182 / 2 = 91. Neither the checker nor Node can spot the mistake on line 3: both lines are correct code that works out a number. Only knowing what the answer should be reveals it.
Key idea
The program applies parentheses, then ** (right to left), then * / %, then + -, left to right within a row. When
you are not sure, add parentheses: they make the order certain and easier to read.
Watch out
Squaring a negative number. Programming languages and calculators disagree about whether −3² means (−3)² or −(3²), so JavaScript refuses to guess, and the checker rejects the expression:
console.log(-3 ** 2);negative-power.ts:1:13 - error TS17006: An unary expression with the '-' operator is not allowed in the left-hand side of an exponentiation expression. Consider enclosing the expression in parentheses.A unary operator is one that works on a single value, like the minus sign in -3. The message asks for parentheses, so write the one you mean:
console.log((-3) ** 2);
console.log(-(3 ** 2));9
-9(-3) ** 2 squares -3, giving 9; -(3 ** 2) squares 3 and then makes it negative, giving -9. Delete negative-power.ts once you have read its message.
Pause and try
Question 10 · Tracing
Predict exactly what this program shows. Show the order of the steps.
console.log(7 + 8 % 3 * 2);
console.log((7 + 8) % 4 * 2);
console.log(2 * 3 ** 2);Check your reasoning
11
6
187 + 8 % 3 * 2:%and*share a row, so left to right:8 % 3is 2, then2 * 2is 4, then7 + 4is 11.(7 + 8) % 4 * 2: parentheses first, 15; then15 % 4is 3; then3 * 2is 6.2 * 3 ** 2:**first, 9; then2 * 9is 18. (Not 36: the program does not multiply 2 × 3 first.)
Reread 2.9 Operator Precedence if any line differed.
Question 11 · Multiple choice
Which expression has the value 25?
-5 ** 2(-5) ** 210 + 5 ** 2 % 25 + 5 * 2
Check your reasoning
B. The parentheses make -5 one number, which is then squared: (-5) × (-5) = 25.
- A is wrong: it has no value at all, because the checker rejects a minus sign directly before
**(TS17006). - C is wrong:
**first gives 25, then25 % 2is 1, then 10 + 1 is 11. - D is wrong:
*before+gives 5 + 10, which is 15. Only(5 + 5) * 2would add first, giving 20.
Reread 2.9 Operator Precedence if you chose differently.
2.10 Converting with Number and String
Numbers do not always arrive as numbers. On Day 20 your programs will read what people type into a web form, and whatever they type arrives as text: the string "15", not the number 15. Files hold text too. The digits look the same, but the type is different, and the type decides what you can do:
const ageText = "15";
console.log(ageText * 2);text-times-number.ts:2:13 - error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
An operand is a value an operator works on, and the left-hand side is the operand to the left of the operator, here ageText. The message lists the types that * accepts there; of those, the only one you know is number (the others are for later or for other situations). So it says: * needs a number on its left, and ageText is a string. The type checker is protecting you: arithmetic on text is almost always a mistake. If you typed this file, fix it with Number() (shown next) or delete it before your next check.
The fix is type conversion: making a new value of a different type from an existing one. Two built-in functions do it:
Number(value)makes a number. Given a string of digits, such as"42"or"1.75", it gives that number.String(value)makes a string: the text you would see if you showed the value.
const fromText = Number("42");
const fromDecimal = Number("1.75");
const asText = String(42);
console.log(fromText, typeof fromText);
console.log(fromDecimal, typeof fromDecimal);
console.log(asText, typeof asText);
console.log(String(true), typeof String(true));42 number
1.75 number
42 string
true string
Line 3’s asText shows as 42, the same as line 1’s number, because console.log shows strings without quotes. Only typeof reveals that it is text. Converting makes a new value; it does not change the original. After Number(priceText), priceText is still a string.
Worked example: Prices that arrive as text
const priceText = "12";
const quantityText = "3";
const price = Number(priceText);
const quantity = Number(quantityText);
console.log("Total:", price * quantity);
console.log(typeof priceText, typeof price);Total: 36
string number| Line | Name | Value | Type |
|---|---|---|---|
| 1 | priceText |
"12" |
string |
| 2 | quantityText |
"3" |
string |
| 3 | price |
12 |
number |
| 4 | quantity |
3 |
number |
Line 5 multiplies the two numbers, 12 × 3 = 36. Line 6 confirms that converting did not change priceText: it is still a string, while price is a number. The checker knows this too: it infers number for price, because Number(...) always gives back a number.
Now vary the text: give Number something that is not a number.
const seats = Number("forty");
console.log(seats, typeof seats);
console.log("Seats for two buses:", seats * 2);NaN number
Seats for two buses: NaN
Number("forty") cannot read a word as a number, so it gives back NaN, short for “Not a Number.” NaN is a special value that means “the result of a calculation that failed.” Its name is confusing, because its type is number, as line 2 shows. And any arithmetic involving NaN gives NaN again, so one bad conversion spreads through every result that uses it.
Notice who reported a problem: nobody. The type checker accepted the program, because Number(...) always gives a number, and seats * 2 is a number times a number. Node ran it without a runtime error, because producing NaN is not an error in JavaScript. The only sign is NaN in the output. (Day 4 shows how a program can check for it.)
Number has a few more rules worth knowing:
console.log(Number(" 12 "));
console.log(Number(""));
console.log(Number("12px"));
console.log(Number("3,5"));12
0
NaN
NaN
Spaces at either end are ignored (" 12 " is 12), and empty text gives 0, not NaN. But anything extra inside makes the whole conversion fail: "12px" is NaN, and so is "3,5", because a number uses a dot, not a comma, for its decimal point.
Key idea
Number(text) makes a number from text and String(value) makes text from a value; both make a new value. Text that
is not a number becomes NaN, a number value meaning “failed,” which neither the checker nor Node reports.
Watch out
NaN far from its cause. Because NaN spreads silently, it often shows up many lines after the conversion that made it. When you see NaN in the output, work backward: find the first variable that holds NaN (show it with console.log), then look at the text that was converted to make it.
Pause and try
Question 12 · Tracing
Predict exactly what this program shows.
console.log(Number("8") * 2);
console.log(String(5 + 5), typeof String(5 + 5));
console.log(Number("8 apples"));
console.log(Number(" 2.5 "));Check your reasoning
16
10 string
NaN
2.5Number("8") * 2converts the text to the number 8, then multiplies: 16.String(5 + 5)works out 10 first, then makes the string"10".console.logshows it without quotes, andtypeofreportsstring."8 apples"has extra characters, soNumbergives NaN." 2.5 "has only spaces around the number, which are ignored: 2.5.
Reread 2.10 Converting with Number and String if any line differed.
Question 13 · Debugging
This program should show how many years until age 18. npx tsc reports an error. Predict the message, explain it, and write the fix.
const ageText = "15";
const yearsLeft = 18 - ageText;
console.log("Years until 18:", yearsLeft);Check your reasoning
years-left.ts:2:24 - error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.This time the string is on the right of - (column 24 is where ageText starts), so the message says right-hand side and has code TS2363 instead of TS2362. ageText holds the string "15", and - needs numbers on both sides. Convert the text first:
const ageText = "15";
const yearsLeft = 18 - Number(ageText);
console.log("Years until 18:", yearsLeft);Years until 18: 3Number(ageText) makes the number 15, so line 2 works out 18 - 15 = 3. ageText itself is still a string.
Reread 2.10 Converting with Number and String if you missed it.
2.11 Floats Are Close, Not Exact
Run this program, and expect a surprise:
console.log(0.1 + 0.2);
console.log(1.1 * 3);
console.log(10 / 3);
console.log(1 + 2);0.30000000000000004
3.3000000000000003
3.3333333333333335
3
0.1 + 0.2 is not exactly 0.3. Nothing is broken; every programming language that uses standard floating-point numbers (explained below) gets the same result. The reason is how a computer stores numbers.
A computer stores numbers in binary, using only the digits 0 and 1. Whole numbers of everyday size convert to binary exactly, so 1 + 2 is exactly 3. Many fractions do not. Think of 1/3 in ordinary decimal: 0.3333…, with threes forever. You can write 0.33 or 0.3333333, but never exactly one third. In binary, 0.1 is like that: it repeats forever, so the computer stores the closest value it can fit. That stored value is a tiny bit off, the tiny errors add up, and sometimes they show in the last digit. A number stored this way is called a floating-point number (or float), and the tiny error is called floating-point imprecision. Every JavaScript number is stored as a float.
The error is tiny (about 0.00000000000000004 here), so it almost never matters for a calculation. It matters when you show the result: nobody wants to read a price of 3.3000000000000003. The fix is to round when you show a value. Math.round rounds to a whole number, so to keep two decimal places, multiply by 100, round, and divide by 100 again: Math.round(x * 100) / 100. For one decimal place, use 10 instead of 100.
Worked example: Splitting a bill
Three friends split a 10-dollar bill.
const bill = 10;
const friends = 3;
const share = bill / friends;
console.log("Exact:", share);
console.log("Rounded:", Math.round(share * 100) / 100);Exact: 3.3333333333333335
Rounded: 3.3310 / 3 is 3.333…, with threes forever, and the stored float runs out of room after about 16 digits, so the last digit is off. Rounding to cents takes three steps:
| Step | Expression | Value |
|---|---|---|
| 1 | share * 100 |
333.33333333333337 |
| 2 | Math.round(...) |
333 |
| 3 | ... / 100 |
3.33 |
share itself is unchanged: Math.round gives back a new value, just like the conversions in 2.10. (Day 3 shows another way to show exactly two decimal places.)
Key idea
Numbers are stored in binary as floats, so many decimals, like 0.1, are stored as a very close approximation, and tiny
errors can show in the last digit. Round when you show a result: Math.round(x * 100) / 100 keeps two decimal places.
Watch out
Rounding too early. Round only when you show a result, not in the middle of a calculation:
const share = Math.round((10 / 3) * 100) / 100;
console.log("Share:", share);
console.log("Three shares:", share * 3);Share: 3.33
Three shares: 9.99Rounding the share to 3.33 first threw away a third of a cent, and multiplying by 3 made the loss visible: three shares total 9.99, not 10. Keep the full value for calculations and round the final answer.
Pause and try
Question 14 · Tracing
Predict exactly what this program shows.
console.log(Math.round((7 / 3) * 100) / 100);
console.log(1.1 * 3);
console.log(Math.round(1.1 * 3 * 10) / 10);Check your reasoning
2.33
3.3000000000000003
3.37 / 3is 2.333…; times 100 is 233.33…, rounded to 233, divided by 100 is 2.33.1.1 * 3shows the float error: 3.3000000000000003.- The same value times 10 comes out as 33 (the tiny error is lost in the multiplication),
Math.round(33)is 33, and 33 / 10 is 3.3.
Reread 2.11 Floats Are Close, Not Exact if any line differed.
Putting it together
Jordan is planning a 523-kilometre road trip at an average 90 km/h. The car uses 6.8 litres of fuel per 100 km, and fuel costs 1.89 per litre. The distance arrives as text, as if typed into a form, so it has to be converted. The comment on line 1 is from Day 1; the blank lines just split the program into steps.
First check. Jordan’s first attempt annotates the distance as a number but forgets to convert the text:
// Trip details: the distance arrives as text, as if typed into a form
const distanceText = "523";
const speedKmh = 90;
const litresPer100Km = 6.8;
const pricePerLitre = 1.89;
const distanceKm: number = distanceText;
console.log("Distance:", distanceKm, "km");road-trip-attempt.ts:7:7 - error TS2322: Type 'string' is not assignable to type 'number'.
The annotation : number on line 7 states Jordan’s intention, so the checker can hold the line to it: distanceText is a string. Nothing has run yet. The fix is to convert with Number (2.10).
Check and run. Jordan fixes line 7 with the conversion. If you typed along, fix road-trip-attempt.ts the same way, or delete it and type the fixed program as road-trip.ts, so that no broken file is left. Now npx tsc reports no errors for the trip program, and node road-trip.ts shows:
// Trip details: the distance arrives as text, as if typed into a form
const distanceText = "523";
const speedKmh = 90;
const litresPer100Km = 6.8;
const pricePerLitre = 1.89;
const distanceKm: number = Number(distanceText);
const totalMinutes = Math.floor((distanceKm * 60) / speedKmh);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
const fuelLitres = (distanceKm / 100) * litresPer100Km;
const fuelCost = fuelLitres * pricePerLitre;
console.log("Distance:", distanceKm, "km");
console.log("Driving time:", hours, "hours", minutes, "minutes");
console.log("Fuel needed:", Math.round(fuelLitres * 10) / 10, "litres");
console.log("Fuel cost:", Math.round(fuelCost * 100) / 100);Distance: 523 km
Driving time: 5 hours 48 minutes
Fuel needed: 35.6 litres
Fuel cost: 67.22
Follow the values and types, in the order the lines run:
| Name (line) | Worked out | Value | Type |
|---|---|---|---|
distanceText (2) |
A string in quotes | "523" |
string |
distanceKm (7) |
Number("523") (2.10) |
523 | number |
totalMinutes (9) |
523 * 60 is 31380, / 90 is 348.67, rounded down (2.8) |
348 | number |
hours (10) |
Math.floor(348 / 60), 5.8 rounded down (2.8) |
5 | number |
minutes (11) |
348 % 60, since 5 × 60 = 300 (2.8) |
48 | number |
fuelLitres (13) |
523 / 100 is 5.23, times 6.8 |
35.564 | number |
fuelCost (14) |
fuelLitres * 1.89 |
67.21596 | number |
Every name is a const (2.2), because nothing changes after it is worked out. The last two lines round only when showing (2.11): 35.564 shows as 35.6 and 67.21596 as 67.22. Notice line 9: the parentheses make the program multiply before dividing, so the minutes are worked out from the full distance and then rounded down once. Writing Math.floor(distanceKm / speedKmh) * 60 instead would round down to 5 whole hours before converting to minutes and lose the 48 extra minutes.
Common mistakes
Every message from today in one place, to look up when you meet one again. All of them come from npx tsc, before anything runs, except the last row.
| Message | Usual cause | Fix |
|---|---|---|
TS2448: Block-scoped variable 'total' used before its declaration. |
Using a variable above the line that declares it | Move the declaration up |
TS2451: Cannot redeclare block-scoped variable 'price'. |
let or const again for an existing name |
Assign without let |
TS2588: Cannot assign to 'busSeats' because it is a constant. |
Assigning to a const |
Use let, or do not change it |
TS2552: Cannot find name 'Score'. Did you mean 'score'? |
Wrong capitals in a variable name | Use the suggested name |
TS1389: 'class' is not allowed as a variable declaration name. |
A reserved word as a name | Choose another name |
TS2322: Type 'string' is not assignable to type 'number'. |
A value of a different type than the variable’s | Fix the value, or the declaration |
TS2322: Type 'null' is not assignable to type 'number'. |
null stored in a number in strict mode |
Store a number |
TS2454: Variable 'finalScore' is used before being assigned. |
Reading a let with no value yet |
Assign a value first |
TS2362 / TS2363: The left-hand side / right-hand side of an arithmetic operation must be of type ... |
Arithmetic with a string | Convert with Number() first |
TS17006: An unary expression with the '-' operator is not allowed ... |
-3 ** 2 |
(-3) ** 2 or -(3 ** 2) |
No message, but NaN or Infinity in the output |
Converting text that is not a number, or dividing by zero | Fix the text, or the divisor |
Vocabulary
| Term | Definition | Example |
|---|---|---|
| variable | A name that holds a value | score |
| variable declaration | A statement that makes a new variable, with let or const |
let score = 10; |
| keyword / reserved word | A word with a fixed meaning in the language / one that cannot be a name | let, const, class |
| assignment operator / assigning | The = sign / storing a value in a variable |
score = 0; |
| constant | A variable declared with const, which can never be assigned again |
const ticketPrice = 12; |
| identifier | A name you choose, following the naming rules | ticketsSold |
| camelCase | Words joined with no spaces, each after the first starting with a capital | maxSpeedKmh |
number |
The type of every number, whole or decimal | 42, 0.45 |
string |
The type of text in quotes | "Hatchet" |
boolean |
The type whose only values are true and false |
const isOpen = true; |
typeof |
Gives a value’s type name as a string while the program runs | typeof 42 is "number" |
| type annotation | : type after a variable’s name, telling the checker its type |
let visitors: number = 0; |
| type inference | The checker working out a type from the starting value | let visitors = 0; is number |
| static typing | A variable’s type is fixed when it is declared and checked before running | count = "five" is rejected |
null / undefined |
Deliberately empty / not assigned yet; each the only value of its own type | const middleName = null; |
remainder (%) / exponent (**) |
What is left over after dividing / raising to a power | 135 % 60 is 15, 2 ** 10 is 1024 |
Math.floor / Math.round |
Round down / round to the nearest whole number | Math.floor(2.25) is 2 |
Infinity |
The number that dividing by zero gives | 12 / 0 |
| operator precedence | The order in which operators are applied | ** before * before + |
| unary operator | An operator that works on one value | the - in -3 |
| operand | A value an operator works on | ageText in ageText * 2 |
| type conversion | Making a new value of another type with Number() or String() |
Number("42") is 42 |
NaN |
“Not a Number”: a number value meaning a calculation failed |
Number("forty") |
| floating-point number / imprecision | A number stored approximately in binary / the tiny errors that causes | 0.1 + 0.2 |
Summary
- A variable holds a value under a name.
constmakes one that can never be assigned again, andletmakes one that can; useconstunless the value must change. Names follow the identifier rules and, by convention, are descriptive camelCase. - Every value has a type:
number(whole or decimal),string,boolean(trueorfalse),null, orundefined.typeofreports it while the program runs (with the quirk thattypeof nullis"object"). - The checker knows each variable’s type from a type annotation or by inference. With static typing, that type never changes:
count = "five"for anumberis rejected before anything runs, and so arenullin anumberand reading a variable before it is assigned. %gives the remainder,**raises to a power,Math.floorrounds down, andMath.roundrounds to the nearest whole number. Precedence: parentheses, then**, then* / %, then+ -, left to right within a row.Number()andString()make new values of another type. Text that is not a number becomesNaN, and dividing by zero givesInfinity; neither the checker nor Node reports them.- Numbers are stored as floats, so
0.1 + 0.2shows a tiny error. Calculate with full values and round only for display, withMath.round(x * 100) / 100.
Practice
Do these in your day02 folder. First make sure npx tsc prints nothing there: if any broken file from today is still in the folder (such as a question’s price-change.ts, lunch-sales.ts, thermostat.ts, or years-left.ts), fix it or delete it. Then type each program yourself, check it with npx tsc, run it with node, and only then open the answer. Change the starting values and run them again to test your program.
Question 15 · Short answer
Fahrenheit to Celsius. Write a program that stores a temperature in Fahrenheit in a constant, converts it to Celsius with the formula C = (F − 32) × 5 ÷ 9, and shows both, with the Celsius value rounded to one decimal place. Try 72 and 212.
Check your reasoning
One possible solution:
const fahrenheit = 72;
const celsius = ((fahrenheit - 32) * 5) / 9;
console.log(fahrenheit, "degrees Fahrenheit is", Math.round(celsius * 10) / 10, "degrees Celsius");72 degrees Fahrenheit is 22.2 degrees CelsiusThe parentheses make the program subtract 32 before multiplying; without them, fahrenheit - 32 * 5 / 9 would multiply and divide first and give a wrong answer. celsius is 22.22222222222222, so Math.round(celsius * 10) / 10 shows 22.2. With 212, the program shows 100. Both values are constants, because neither changes after it is worked out.
Reread 2.9 Operator Precedence and 2.11 Floats Are Close, Not Exact if your result differed.
Question 16 · Short answer
Seconds to hours, minutes, and seconds. Store a number of seconds in a constant, such as 7384. Use Math.floor and % to work out the hours, the minutes, and the seconds left over, and show them on one line.
Check your reasoning
One possible solution:
const totalSeconds = 7384;
const hours = Math.floor(totalSeconds / 3600);
const secondsLeft = totalSeconds % 3600;
const minutes = Math.floor(secondsLeft / 60);
const seconds = secondsLeft % 60;
console.log(totalSeconds, "seconds is", hours, "hours,", minutes, "minutes and", seconds, "seconds");7384 seconds is 2 hours, 3 minutes and 4 seconds| Line | Expression | Worked out | Value |
|---|---|---|---|
| 2 | Math.floor(totalSeconds / 3600) |
3600 seconds per hour | 2 |
| 3 | totalSeconds % 3600 |
7384 - 7200 | 184 |
| 4 | Math.floor(secondsLeft / 60) |
whole minutes in 184 | 3 |
| 5 | secondsLeft % 60 |
184 - 180 | 4 |
Check: 2 × 3600 + 3 × 60 + 4 = 7384. If you used / without Math.floor, you got decimals like 2.0511….
Reread 2.8 Remainders, Powers, and Rounding if your result differed.
Question 17 · Short answer
Compound interest. A savings account starts with a principal (the starting amount) and grows by a fixed rate each year, with each year’s interest added to the balance. After years years the balance is principal × (1 + rate) to the power years. Store a principal of 1000, a rate of 0.05 (5%), and 10 years in constants; show the final amount and the interest earned, both rounded to two decimal places.
Check your reasoning
One possible solution:
const principal = 1000;
const rate = 0.05;
const years = 10;
const amount = principal * (1 + rate) ** years;
const interest = amount - principal;
console.log("Final amount:", Math.round(amount * 100) / 100);
console.log("Interest earned:", Math.round(interest * 100) / 100);Final amount: 1628.89
Interest earned: 628.89(1 + rate) ** years needs the parentheses: ** comes before +, so 1 + rate ** years would raise only 0.05 to the 10th power. The balance is then multiplied by 1000, giving 1628.894626777442. The interest is that amount minus the principal. Both are rounded only when shown, so no accuracy is lost along the way.
Reread 2.9 Operator Precedence and 2.11 Floats Are Close, Not Exact if your result differed.
Review questions
Question 18 · Multiple choice
A program starts with let rainfall = 12.5;. Which next line does npx tsc accept?
rainfall = "13";rainfall = 13;rainfall = null;let rainfall = 13;
Check your reasoning
B. The checker inferred number from 12.5, and 13 is also a number: whole numbers and decimals share one type, so the assignment fits.
- A is wrong: the quotes make
"13"a string, and a variable keeps its type, so this is TS2322. - C is wrong: in strict mode,
nullis not a number, so this is TS2322 too. - D is wrong:
letwould declare a second variable namedrainfall, which is TS2451. Leave outletto assign to the existing one.
Reread 2.6 Static Typing: A Variable Keeps Its Type and 2.7 Null and Undefined if you chose differently.
Question 19 · Tracing
This program makes change for 87 cents using quarters (25 cents) and dimes (10 cents). What does it show? Track cents, quarters, and dimes.
let cents = 87;
const quarters = Math.floor(cents / 25);
cents = cents % 25;
const dimes = Math.floor(cents / 10);
cents = cents % 10;
console.log(quarters, dimes, cents);Check your reasoning
3 1 2| Line and work | cents |
quarters |
dimes |
|---|---|---|---|
1: 87 |
87 | ||
2: Math.floor(87 / 25) |
87 | 3 | |
3: 87 % 25 |
12 | 3 | |
4: Math.floor(12 / 10) |
12 | 3 | 1 |
5: 12 % 10 |
2 | 3 | 1 |
Line 3 replaces cents with what is left after the quarters, 87 - 75 = 12, and line 4 uses that new value. The answer is 3 quarters, 1 dime, and 2 cents left. If you got 3 8 7, you used 87 instead of 12 on lines 4 and 5, forgetting that line 3 changed cents. That is why cents is a let and the other two are const.
Reread 2.1 Variables: Giving a Value a Name and 2.8 Remainders, Powers, and Rounding if your output differed.
Question 20 · Debugging
Lena wants to show the average of the scores 80 and 90, rounded to a whole number. Her program has two mistakes: one makes npx tsc reject it, and one would give the wrong answer even after the first is fixed. Find both and write the fixed program.
const scoreA = 80;
const scoreB = 90;
const average = scoreA + scoreB / 2;
average = Math.round(average);
console.log("Average:", average);Check your reasoning
The first mistake is on line 4, which assigns to the constant average:
average-two-mistakes.ts:4:1 - error TS2588: Cannot assign to 'average' because it is a constant.Changing const to let would satisfy the checker, but the program would then show 125, because / comes before +: it works out 90 / 2 = 45 and adds 80. The second fix is parentheses around the sum. Doing the rounding in the same line removes the need to change average at all, so it can stay a const:
const scoreA = 80;
const scoreB = 90;
const average = Math.round((scoreA + scoreB) / 2);
console.log("Average:", average);Average: 85The first mistake was found before anything ran. The second gave no message at all; only knowing the right answer (85) reveals it. If you typed the original program, fix or delete it before you go on.
Reread 2.2 Constants: Names That Never Change and 2.9 Operator Precedence if you missed either one.
Question 21 · Tracing
Predict exactly what this program shows, values and types.
const half = 7 / 2;
const doubledText = String(half * 2);
const fromWord = Number("seven");
console.log(half, typeof half);
console.log(doubledText, typeof doubledText);
console.log(fromWord, typeof fromWord);
console.log(typeof null);Check your reasoning
3.5 number
7 string
NaN number
object| Line | Worked out | Value | Type |
|---|---|---|---|
| 1 | 7 / 2 |
3.5 |
number |
| 2 | half * 2 is 7, then String(7) |
"7" |
string |
| 3 | Number("seven") cannot read a word |
NaN |
number |
Line 5 shows 7 without quotes, so only typeof reveals that it is text. Line 6 shows that NaN, despite its name, is a number. Line 7 is the quirk from 2.7: typeof null is "object".
Reread 2.10 Converting with Number and String and 2.7 Null and Undefined if your output differed.
Question 22 · Short answer
Mia’s program shows Price for 3: NaN, even though npx tsc reported no errors for it and node reported no error. Explain why neither the type checker nor Node reported a problem, and fix the program.
const priceText = "4.50 dollars";
const price = Number(priceText);
console.log("Price for 3:", price * 3);Check your reasoning
Model answer: "4.50 dollars" contains a word, so Number cannot read it and gives NaN. The type checker accepts the program, because Number(...) always gives a number and a number times 3 is a number: the checker checks the kind of each value, not whether the value makes sense. Node reports nothing either, because making NaN is not a runtime error in JavaScript; the program simply runs on with it. Give Number only the digits:
const priceText = "4.50";
const price = Number(priceText);
console.log("Price for 3:", price * 3);Price for 3: 13.5A complete answer must:
- say that
Number("4.50 dollars")givesNaNbecause of the extra word, - explain that the checker sees only the type (
number), and thatNaNis not a runtime error, and - remove the extra text so the string holds only a number.
Reread 2.10 Converting with Number and String if your reasoning differed.
Question 23 · Multiple choice
This program shows four lines. Which line does not show 4?
console.log(Math.round(3.5));
console.log(Math.floor(4.99));
console.log(14 % 5);
console.log(-Math.floor(-4.5));- Line 1
- Line 2
- Line 3
- Line 4
Check your reasoning
D. Math.floor(-4.5) rounds down, toward the lower number, which is -5. The minus sign in front then makes it 5.
- A shows 4: 3.5 is exactly halfway, and halves round up.
- B shows 4:
Math.flooralways rounds down, so 4.99 becomes 4, however close it is to 5. - C shows 4: two 5s make 10, and 14 - 10 leaves 4.
If you picked A, you may have expected halves to round down; if you picked B, you rounded to the nearest number instead of down.
Reread 2.8 Remainders, Powers, and Rounding if you chose differently.
Go further
The TypeScript Handbook covers today’s types and annotations in more detail, and MDN explains declarations and JavaScript’s values: TypeScript Handbook: Everyday Types (read the primitives and type annotations sections) and MDN: Grammar and types.
Reflection
Check each statement. If the answer is “not yet,” reread the linked subsection.
- I can create a variable with let and trace how its value changes. Reread
- I can choose between const and let, and explain why const is the default. Reread
- I can tell whether a name is legal and write it in camelCase. Reread
- I can name the type of a number, a string, or a boolean, and use typeof. Reread
- I can write a type annotation and say what type the checker infers without one. Reread
- I can say whether the type checker will accept an assignment. Reread
- I can explain null and undefined and what strict mode does with them. Reread
- I can use %, **, Math.floor, and Math.round, including with negative numbers. Reread
- I can evaluate an expression in precedence order. Reread
- I can convert values with Number() and String() and explain where NaN comes from. Reread
- I can explain why 0.1 + 0.2 is not exactly 0.3 and round a result for display. Reread