Warm-up · Activity 1 of 7
// B1.5 · ~45 min · Beginner
Build: type-check an existing JavaScript script
After this lesson you can take a working JavaScript script, rename it to main.ts, fix every diagnostic tsc reports under strict settings, and show that it still prints exactly the same.
Lesson 5 of 5 in B1 Setup and the basics
You will be able to
- Convert a script: rename it, run npx tsc, and compare the output of node main.ts with the JavaScript version
- Annotate the parameters and return types that tsc reports, and fix the errors those annotations reveal
- Fix values JavaScript converted silently (parseInt, isNaN, text in arithmetic) without changing the output
Predict · Activity 2 of 7
Predict before you read on: this JavaScript prints 37. Renamed to main.ts, how many diagnostics does npx tsc print?
function percent(part, total) { return part / total * 100; } console.log(parseInt(percent(3, 8)));Practice · Activity 3 of 7
tsc reported Parameter 'scores' implicitly has an 'any' type. Fill in the type: scores is looped over, each item is added to a number, and the call passes [92, 88, 95].
function average(scores: ____): number { let sum = 0; for (const score of scores) { sum += score; } return sum / scores.length; } console.log(average([92, 88, 95]).toFixed(1));function average(scores: ): number {Practice · Activity 4 of 7
tsc reports TS7006 for price and qty on line 1. You change line 1 to function lineTotal(price: number, qty: number): number { and run npx tsc again. What does it report now?
function lineTotal(price, qty) { return price * qty; } const prices = [2.5, 1.2, 4]; const qtys = ["2", "5", "1"]; let sum = 0; for (let i = 0; i < prices.length; i++) { sum += lineTotal(prices[i], qtys[i]); } console.log("Sum:", sum.toFixed(2));Practice · Activity 5 of 7
The JavaScript version printed 37. Which new line 4 makes tsc report no errors and still prints 37?
function percent(part: number, total: number): number { return part / total * 100; } console.log(parseInt(percent(3, 8)));Brain teaser · Activity 6 of 7
Brain teaser. tsc reports Parameter 'name' implicitly has an 'any' type. You write name: any instead of name: string. How many errors does npx tsc report now?
function greet(name) { return "Hi " + name.toUppercase(); } console.log(greet("Ada"));Apply · Activity 7 of 7
Mini-task. This JavaScript prints Words: 5, Longest: checking and Over the limit: false. Save that output, put the code in main.ts and run npx tsc. Annotate both parameters and both return types, run tsc again, and fix the diagnostic that appears then. Finish when tsc prints nothing and node main.ts prints the same three lines.
function countWords(text) { return text.split(" ").length; } function longest(words) { let best = ""; for (const word of words) { if (word.length > best.length) best = word; } return best; } const line = "type checking catches mistakes early"; const limit = "5"; console.log("Words:", countWords(line)); console.log("Longest:", longest(line.split(" "))); console.log("Over the limit:", countWords(line) > limit);Check your work against this list
Build it yourself
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
A weather log, converted
As main.js, this script printed exactly the output below. Renamed to main.ts, tsc reported three diagnostics: main.ts(1,23): error TS7006 for celsius, main.ts(5,18): error TS7006 for values, and main.ts(20,40): error TS2345 for parseInt(mean), because parseInt expects text. The fixes were number and number[] on the parameters, number as both return types, and Math.trunc(mean), which drops the decimals as parseInt did. The variables needed no annotations: TypeScript infers them. Run npx tsc, which prints nothing, then node main.ts, and compare.
main.ts
function toFahrenheit(celsius: number): number {
return celsius * 9 / 5 + 32;
}
function average(values: number[]): number {
let sum = 0;
for (const value of values) {
sum += value;
}
return sum / values.length;
}
const readings = [18.5, 21, 23.25, 19];
const days = ["Mon", "Tue", "Wed", "Thu"];
for (let i = 0; i < readings.length; i++) {
console.log(days[i], readings[i], "C is", toFahrenheit(readings[i]), "F");
}
const mean = average(readings);
console.log("Average:", mean.toFixed(2), "C");
console.log("Whole degrees:", Math.trunc(mean));
Run it with
npx tsc
node main.tsOutput
Mon 18.5 C is 65.3 F
Tue 21 C is 69.8 F
Wed 23.25 C is 73.85 F
Thu 19 C is 66.2 F
Average: 20.44 C
Whole degrees: 20- The output is identical to what main.js printed: the annotations are removed before Node.js runs the file.
- readings, days, mean and the loop variables have no annotations; TypeScript inferred number[], string[] and number.
- Math.trunc(20.4375) is 20, the same as parseInt gave, so the last line did not change.
- The line numbers of the three diagnostics are the same in main.js and main.ts: only types were added.
Change it and run it
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.
Exercises
Exercise 1 of 3
Annotate the converted functions
This was main.js; it has just been renamed to main.ts. It works, but tsc reports TS7006 four times. Annotate every parameter and every return type. Do not change anything else: the tests call the functions and check that the program still prints Berlin: 69.8 F.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.
Hints
Hint 1
celsius is multiplied and divided, so it is a number. values is looped over and summed: number[].
Hint 2
city is joined into text and degrees has toFixed called on it: string and number.
Hint 3
Each return type goes after the parameter list: function label(city: string, degrees: number): string {
Show a solution
One way to solve it. Yours can look different and still pass the checks.
export function toFahrenheit(celsius: number): number {
return celsius * 9 / 5 + 32;
}
export function average(values: number[]): number {
let sum = 0;
for (const value of values) {
sum += value;
}
return sum / values.length;
}
export function label(city: string, degrees: number): string {
return city + ": " + degrees.toFixed(1) + " F";
}
console.log(label("Berlin", toFahrenheit(average([18, 21, 24]))));
Run it on your computer
Install TypeScript 7.0 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.ts
export function toFahrenheit(celsius) {
return celsius * 9 / 5 + 32;
}
export function average(values) {
let sum = 0;
for (const value of values) {
sum += value;
}
return sum / values.length;
}
export function label(city, degrees) {
return city + ": " + degrees.toFixed(1) + " F";
}
console.log(label("Berlin", toFahrenheit(average([18, 21, 24]))));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {toFahrenheit, average, label} from './main.ts';
test('toFahrenheit(100) is 212', () => {
assert.equal(toFahrenheit(100), 212, 'toFahrenheit(100) should be 212');
});
test('average([1, 2, 3, 4]) is 2.5', () => {
assert.equal(average([1, 2, 3, 4]), 2.5, 'average([1, 2, 3, 4]) should be 2.5');
});
test('label("Oslo", 50) is Oslo: 50.0 F', () => {
assert.equal(label('Oslo', 50), 'Oslo: 50.0 F', 'label("Oslo", 50) should be Oslo: 50.0 F');
});
test('the program still prints Berlin: 69.8 F', async () => {
const out = (await runMain()).trimEnd();
assert.equal(out, 'Berlin: 69.8 F', `the program printed ${JSON.stringify(out)}`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc needs TypeScript in the folder: run npm install --save-dev typescript there once. tsc checks the types; Node.js runs main.ts by removing them.
Run the program:
npx tsc
node main.tsRun the checks (needs learnrun.js in the same folder):
npx tsc
node --testDownload learnrun.jsExercise 2 of 3
Fix what JavaScript converted silently
The parameters are annotated already, and the program prints 4 true false 42, which is right. tsc still reports three errors, each a place where JavaScript converted a value by itself. Fix each one so that it type-checks, the functions keep their results and the program prints exactly the same. Leave the annotations as they are.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.
Hints
Hint 1
parseInt expects text. Math.trunc(x) drops the decimals of a number, as parseInt did here.
Hint 2
isNaN expects a number. Convert the text on purpose first: isNaN(Number(text)).
Hint 3
text * 2 multiplies text. Number(text) * 2 says what JavaScript did silently.
Show a solution
One way to solve it. Yours can look different and still pass the checks.
export function wholePages(bytes: number, perPage: number): number {
return Math.trunc(bytes / perPage);
}
export function isNumber(text: string): boolean {
return !isNaN(Number(text));
}
export function double(text: string): number {
return Number(text) * 2;
}
console.log(wholePages(5000, 1024), isNumber("12"), isNumber("abc"), double("21"));
Run it on your computer
Install TypeScript 7.0 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.ts
export function wholePages(bytes: number, perPage: number): number {
return parseInt(bytes / perPage);
}
export function isNumber(text: string): boolean {
return !isNaN(text);
}
export function double(text: string): number {
return text * 2;
}
console.log(wholePages(5000, 1024), isNumber("12"), isNumber("abc"), double("21"));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {wholePages, isNumber, double} from './main.ts';
test('wholePages keeps only whole pages', () => {
assert.equal(wholePages(5000, 1024), 4, 'wholePages(5000, 1024) should be 4');
assert.equal(wholePages(2048, 1024), 2, 'wholePages(2048, 1024) should be 2');
});
test('isNumber tells numbers from other text', () => {
assert.equal(isNumber('12'), true, 'isNumber("12") should be true');
assert.equal(isNumber('abc'), false, 'isNumber("abc") should be false');
});
test('double("21") is 42', () => {
assert.equal(double('21'), 42, 'double("21") should be the number 42');
});
test('the program still prints 4 true false 42', async () => {
const out = (await runMain()).trimEnd();
assert.equal(out, '4 true false 42', `the program printed ${JSON.stringify(out)}`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc needs TypeScript in the folder: run npm install --save-dev typescript there once. tsc checks the types; Node.js runs main.ts by removing them.
Run the program:
npx tsc
node main.tsRun the checks (needs learnrun.js in the same folder):
npx tsc
node --testDownload learnrun.jsExercise 3 of 3
Build: type-check the grade report
The module build. This grade report was main.js; it prints seven correct lines. Make it type-check under the course tsconfig without changing a single character of its output. Run npx tsc (or the tests) and fix the first diagnostic, then run again. Expect seven diagnostics at first, and one more after you annotate grade. Annotate every parameter and return type, and fix each mismatch where the value comes from. The tests compare the output with the JavaScript version.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.
Hints
Hint 1
Start with the four TS7006 errors: scores is number[], avg is a number, name is a string. Add return types too: number, string, string.
Hint 2
avg >= passMark compares a number with "50", and bonus * 1 multiplies "5". Store both values as numbers where they are declared.
Hint 3
parseInt(classAverage) becomes Math.trunc(classAverage). After avg: number, grade("100") is reported: pass the number 100.
Show a solution
One way to solve it. Yours can look different and still pass the checks.
// Grade report: each student's average, then a summary.
function average(scores: number[]): number {
let sum = 0;
for (const score of scores) {
sum += score;
}
return sum / scores.length;
}
function grade(avg: number): string {
if (avg >= 85) return "A";
if (avg >= 70) return "B";
if (avg >= 50) return "C";
return "F";
}
function formatLine(name: string, avg: number): string {
return name.padEnd(6) + avg.toFixed(1) + " " + grade(avg);
}
const names = ["Ada", "Grace", "Linus"];
const scores = [[92, 88, 95], [71, 64, 80], [45, 52, 38]];
const passMark = 50;
const bonus = 5;
let passed = 0;
let classTotal = 0;
for (let i = 0; i < names.length; i++) {
const avg = average(scores[i]);
classTotal += avg;
console.log(formatLine(names[i], avg));
if (avg >= passMark) passed++;
}
const classAverage = classTotal / names.length;
console.log("Passed:", passed, "of", names.length);
console.log("Class average, whole points:", Math.trunc(classAverage));
console.log("With bonus:", classAverage + bonus * 1);
console.log("A perfect 100 is grade", grade(100));
Run it on your computer
Install TypeScript 7.0 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.ts
// Grade report: each student's average, then a summary.
function average(scores) {
let sum = 0;
for (const score of scores) {
sum += score;
}
return sum / scores.length;
}
function grade(avg) {
if (avg >= 85) return "A";
if (avg >= 70) return "B";
if (avg >= 50) return "C";
return "F";
}
function formatLine(name, avg) {
return name.padEnd(6) + avg.toFixed(1) + " " + grade(avg);
}
const names = ["Ada", "Grace", "Linus"];
const scores = [[92, 88, 95], [71, 64, 80], [45, 52, 38]];
const passMark = "50";
const bonus = "5";
let passed = 0;
let classTotal = 0;
for (let i = 0; i < names.length; i++) {
const avg = average(scores[i]);
classTotal += avg;
console.log(formatLine(names[i], avg));
if (avg >= passMark) passed++;
}
const classAverage = classTotal / names.length;
console.log("Passed:", passed, "of", names.length);
console.log("Class average, whole points:", parseInt(classAverage));
console.log("With bonus:", classAverage + bonus * 1);
console.log("A perfect 100 is grade", grade("100"));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
const lines = async () => (await runMain()).trimEnd().split('\n');
test('the three student lines are unchanged', async () => {
const out = (await lines()).slice(0, 3).join('\n');
assert.equal(out, 'Ada 91.7 A\nGrace 71.7 B\nLinus 45.0 F', `the student lines were ${JSON.stringify(out)}`);
});
test('the summary lines are unchanged', async () => {
const out = (await lines()).slice(3).join('\n');
assert.equal(
out,
'Passed: 2 of 3\nClass average, whole points: 69\nWith bonus: 74.44444444444444\nA perfect 100 is grade A',
`the summary lines were ${JSON.stringify(out)}`
);
});
test('the report still has exactly seven lines', async () => {
const count = (await lines()).length;
assert.equal(count, 7, `the program printed ${count} lines`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc needs TypeScript in the folder: run npm install --save-dev typescript there once. tsc checks the types; Node.js runs main.ts by removing them.
Run the program:
npx tsc
node main.tsRun the checks (needs learnrun.js in the same folder):
npx tsc
node --testDownload learnrun.jsCommon mistakes
An array parameter annotated as a single number
function average(scores: number) {
let sum = 0;
for (const score of scores) {
sum += score;
}
return sum / scores.length;
}
console.log(average(4));
What tsc or Node.js prints
main.ts(3,23): error TS2488: Type 'number' must have a '[Symbol.iterator]()' method that returns an iterator.Why, and the fix
number is one number, and one number cannot be looped over with for...of: that needs an iterable, such as an array. tsc also reports that number has no length. The body loops over scores, so it is an array of numbers: scores: number[]. Then change the call to pass an array as well: average([4]).
A return type that does not match what the function returns
function label(avg: number): number {
return avg >= 50 ? "pass" : "fail";
}
console.log(label(70));
What tsc or Node.js prints
main.ts(2,22): error TS2322: Type 'string' is not assignable to type 'number'.Why, and the fix
The annotation after the parameter list says what the function returns, and tsc checks every return against it. Here both results are text, so tsc reports "pass" at column 22 and "fail" at column 31. The code is right and the annotation is wrong: write function label(avg: number): string.
Number with a capital N as the type
function double(n: Number) {
return n * 2;
}
console.log(double(4));
What tsc or Node.js prints
main.ts(2,10): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.Why, and the fix
The type of a number is number, in lower case. The Handbook says the capitalised names String, Number and Boolean refer to special built-in types that very rarely appear in your code, and * does not accept a Number. Write n: number, and use string and boolean in lower case too.
TypeScript in the browser: the TypeScript 6.0.3 compiler, Apache-2.0, then your browser’s own engine. Licence and source
Exit ticket
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.