Warm-up · Activity 1 of 7
Warm-up from JavaScript: len is called with three kinds of value. What does this print?
function len(x) {
return x.length;
}
console.log(len("hello"), len([1, 2]), len(42));// B4.3 · ~30 min · Beginner
After this lesson you can write a function that callers may call in several ways, read the errors tsc gives for it, and tell when a union parameter is simpler.
You will be able to
Warm-up · Activity 1 of 7
function len(x) {
return x.length;
}
console.log(len("hello"), len([1, 2]), len(42));Predict · Activity 2 of 7
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);Practice · Activity 3 of 7
function toCents(euros: number): number;
function toCents(euros: number[]): number[];
function toCents(euros: ____): number | number[] {
if (Array.isArray(euros)) return euros.map((e) => Math.round(e * 100));
return Math.round(euros * 100);
}
console.log(toCents(4.99), toCents([1.5, 0.25]));Practice · Activity 4 of 7
function pad(text: string): string;
function pad(text: string, width: number, fill: string): string;
function pad(text: string, width = 8, fill = " "): string {
return text.padStart(width, fill);
}
pad("7");
pad("7", 3);
pad("7", 3, "0");Practice · Activity 5 of 7
function fn(x: string): string;
function fn(x: number): boolean;
function fn(x: string | number) {
return "oops";
}Brain teaser · Activity 6 of 7
function greet(name: string): string;
function greet(names: string[]): string;
function greet(input?: string | string[]): string {
if (input === undefined) return "Hello, nobody";
return "Hello, " + (Array.isArray(input) ? input.join(" and ") : input);
}
console.log(greet(["Ada", "Grace"]));
console.log(greet());Apply · Activity 7 of 7
Check your work against this list
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
makeDate is called with a timestamp or with year, month and day: the argument counts differ, so it needs overloads. toCents returns a number for a number and an array for an array: the return type depends on the argument, so it has overloads too. len returns a number either way, so one union parameter is enough. Run npx tsc, then node main.ts. Then add the line makeDate(2026, 10); and run tsc again.
main.ts
// One function, two ways to call it: overload signatures, then one implementation.
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
if (month !== undefined && day !== undefined) {
return new Date(Date.UTC(yearOrTimestamp, month - 1, day));
}
return new Date(yearOrTimestamp);
}
// The return type follows the argument: a number gives a number, an array an array.
function toCents(euros: number): number;
function toCents(euros: number[]): number[];
function toCents(euros: number | number[]): number | number[] {
if (Array.isArray(euros)) return euros.map((e) => Math.round(e * 100));
return Math.round(euros * 100);
}
// The same return type either way: a union parameter is simpler.
function len(x: string | number[]): number {
return x.length;
}
const day = (date: Date) => date.toISOString().slice(0, 10);
console.log(day(makeDate(0)), day(makeDate(2026, 10, 4)));
const one = toCents(4.99); // number
const many = toCents([1.5, 0.25]); // number[]
console.log(one + 1, many.join(" + "));
const input = Math.random() > 0.5 ? "hello" : [1, 2, 3, 4, 5];
console.log(len(input));
Run it with
npx tsc
node main.tsOutput
1970-01-01 2026-10-04
500 150 + 25
5Tab 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.
Exercise 1 of 2
duration(90) should give 1:30, and duration(2, 5), with minutes and seconds, 2:05. The two overloads are there, and tsc accepts the file: an implementation with fewer parameters counts as compatible. But it ignores the second argument, so duration(2, 5) gives 0:02. Change the implementation signature so that it receives both arguments, the second one optional, and work out the total seconds for either way of calling. Keep the overloads 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.
The implementation must accept what both overloads pass: one number, or two. Make the second parameter optional.
Name the parameters for what they may hold, for example (first: number, second?: number).
const total = second === undefined ? first : first * 60 + second; then compute minutes and seconds from total as before.
One way to solve it. Yours can look different and still pass the checks.
export function duration(seconds: number): string;
export function duration(minutes: number, seconds: number): string;
export function duration(first: number, second?: number): string {
const total = second === undefined ? first : first * 60 + second;
const minutes = Math.floor(total / 60);
return minutes + ":" + String(total % 60).padStart(2, "0");
}
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 duration(seconds: number): string;
export function duration(minutes: number, seconds: number): string;
export function duration(total: number): string {
const minutes = Math.floor(total / 60);
return minutes + ":" + String(total % 60).padStart(2, "0");
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {duration} from './main.ts';
test('duration(90) is 1:30', () => {
assert.equal(duration(90), '1:30', `duration(90) gave ${JSON.stringify(duration(90))}`);
});
test('duration(5) is 0:05', () => {
assert.equal(duration(5), '0:05', `duration(5) gave ${JSON.stringify(duration(5))}`);
});
test('duration(2, 5) is 2:05', () => {
assert.equal(duration(2, 5), '2:05', `duration(2, 5) gave ${JSON.stringify(duration(2, 5))}`);
});
test('duration(1, 75) carries the seconds over: 2:15', () => {
assert.equal(duration(1, 75), '2:15', `duration(1, 75) gave ${JSON.stringify(duration(1, 75))}`);
});
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 2
wordCount counts the words in a string or in an array of lines. countAll passes a string | string[] on to it, and tsc reports error TS2769: No overload matches this call. Both overloads take one argument and return a number, so replace them with a single signature: keep the implementation, with its union parameter, and delete the two overload lines. Keep countAll as it is.
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.
TypeScript resolves a call to one overload, and input in countAll might be either kind of value.
The implementation signature already has the union parameter. Once the overloads are gone, it is the one callers see.
Delete the lines export function wordCount(text: string): number; and export function wordCount(lines: string[]): number;.
One way to solve it. Yours can look different and still pass the checks.
export function wordCount(input: string | string[]): number {
const text = Array.isArray(input) ? input.join(" ") : input;
return text.split(" ").filter((word) => word !== "").length;
}
export function countAll(input: string | string[]): number {
return wordCount(input);
}
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 wordCount(text: string): number;
export function wordCount(lines: string[]): number;
export function wordCount(input: string | string[]): number {
const text = Array.isArray(input) ? input.join(" ") : input;
return text.split(" ").filter((word) => word !== "").length;
}
export function countAll(input: string | string[]): number {
return wordCount(input);
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {wordCount, countAll} from './main.ts';
test('wordCount counts the words in a string', () => {
assert.equal(wordCount('to be or not'), 4, `wordCount('to be or not') gave ${wordCount('to be or not')}`);
});
test('wordCount counts the words in all lines', () => {
assert.equal(wordCount(['a b', 'c']), 3, `wordCount(['a b', 'c']) gave ${wordCount(['a b', 'c'])}`);
});
test('countAll works for a string and for lines', () => {
assert.equal(countAll('one two'), 2, `countAll('one two') gave ${countAll('one two')}`);
assert.equal(countAll(['one', 'two three']), 3, `countAll(['one', 'two three']) gave ${countAll(['one', 'two three'])}`);
});
test('an empty string has no words', () => {
assert.equal(wordCount(''), 0, `wordCount('') gave ${wordCount('')}`);
});
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.jsfunction pad(text: string): string;
function pad(text: string, width: number, fill: string): string;
function pad(text: string, width = 8, fill = " "): string {
return text.padStart(width, fill);
}
console.log(pad("7", 3));
What tsc or Node.js prints
main.ts(7,13): error TS2575: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.Why, and the fix
The defaults belong to the implementation signature, and callers cannot see it: only the overloads count. Add the missing way of calling as an overload, pad(text: string, width: number): string;, or, as nothing here changes the return type, drop the overloads and keep one signature with the defaults.
function parse(text: string): number;
function parse(text: string | number): number {
return Number(text);
}
console.log(parse(42));
What tsc or Node.js prints
main.ts(5,19): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.Why, and the fix
With one overload, callers see only parse(text: string), although the implementation accepts numbers too. The Handbook says to always write two or more overloads above the implementation. Here no overload is needed at all: delete the first line, and function parse(text: string | number): number is what callers see.
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
console.log(len(Math.random() > 0.5 ? "hello" : [0]));
What tsc or Node.js prints
main.ts(7,17): error TS2769: No overload matches this call.Why, and the fix
The argument is a string or an array, and TypeScript resolves a call to a single overload; neither takes both. The lines after the message show what each overload rejected. As both overloads return a number, replace them with one signature: function len(x: any[] | string) { return x.length; }.
TypeScript in the browser: the TypeScript 6.0.3 compiler, Apache-2.0, then your browser’s own engine. Licence and source
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.