Warm-up · Activity 1 of 7
// B2.4 · ~28 min · Beginner
Type aliases versus interfaces
After this lesson you can name a type once with type or interface, build one type on another, and choose between the two for a good reason.
You will be able to
- Name an object type with a type alias or an interface, and use the name in annotations
- Build a type on another: an interface with extends, a type alias with &
- Explain the key difference, declaration merging, and choose between type and interface
Predict · Activity 2 of 7
Predict before you read on: a product id is stored in a variable meant for a user id. What does tsc report?
type UserId = string; type ProductId = string; let current: UserId = "u-17"; const product: ProductId = "p-9"; current = product; console.log(current);Practice · Activity 3 of 7
Fill in the keyword that makes Dog have every property of Animal, so that tsc accepts rex and the program prints Rex the Collie.
interface Animal { name: string; } interface Dog ____ Animal { breed: string; } const rex: Dog = { name: "Rex", breed: "Collie" }; console.log(rex.name + " the " + rex.breed);interface Dog Animal {Practice · Activity 4 of 7
Person is an intersection of two aliases. What does tsc report first?
type Named = { name: string }; type Aged = { age: number }; type Person = Named & Aged; const ada: Person = { name: "Ada" };Practice · Activity 5 of 7
Match each declaration to what it does.
Brain teaser · Activity 6 of 7
Brain teaser. Box is declared twice, and box has only a width. What does tsc report?
interface Box { width: number; } interface Box { height: number; } const box: Box = { width: 2 };Apply · Activity 7 of 7
Mini-task. Declare an interface Person with a name and an optional email, and an interface Employee that extends Person with a team. Write badge(employee: Employee), which returns text such as Ada (Compilers), and call it once. Then write Employee as a type alias with & instead, and check that tsc still accepts the same code.
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
Named types for a small catalogue
The catalogue holds books and films. ID is a type alias that accepts a number or a string. Item is an interface with the properties both kinds share. Book extends Item; Film is a type alias that combines Item with & instead. Each function names the type once in its signature. Run npx tsc, then node main.ts. Then leave out pages in the last call, or declare interface Item a second time with one more property, and read what tsc says.
main.ts
// Named types for a small library catalogue.
type ID = number | string;
interface Item {
id: ID;
title: string;
}
interface Book extends Item {
author: string;
pages: number;
}
type Film = Item & {
minutes: number;
};
function describeBook(book: Book): string {
return book.id + ": " + book.title + " by " + book.author + ", " + book.pages + " pages";
}
function describeFilm(film: Film): string {
return film.id + ": " + film.title + ", " + film.minutes + " min";
}
const dune: Book = { id: 1, title: "Dune", author: "Frank Herbert", pages: 412 };
const metropolis: Film = { id: "F-7", title: "Metropolis", minutes: 153 };
console.log(describeBook(dune));
console.log(describeFilm(metropolis));
console.log(describeBook({ id: 2, title: "Emma", author: "Jane Austen", pages: 474 }));
Run it with
npx tsc
node main.tsOutput
1: Dune by Frank Herbert, 412 pages
F-7: Metropolis, 153 min
2: Emma by Jane Austen, 474 pages- dune has a number id and metropolis a string id: ID accepts both.
- Book and Film each get id and title from Item, one through extends, one through &.
- The last call passes an object literal straight to describeBook; it is checked against Book like any other.
- The names exist only for tsc: Node.js strips them, and the output is what plain JavaScript prints.
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 2
Name the type both functions use
Both functions take a Product, but tsc reports Cannot find name 'Product'. Declare Product above them, as an interface or a type alias, with a name (string) and a price (number). Change nothing in the functions. Run the tests: the first one is the type-check.
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
Both diagnostics are the same: the name Product is used but never declared.
Hint 2
An interface has no =: interface Product { … }. A type alias has one: type Product = { … };
Hint 3
interface Product { name: string; price: number; } above the first function is enough.
Show a solution
One way to solve it. Yours can look different and still pass the checks.
interface Product {
name: string;
price: number;
}
export function label(product: Product): string {
return product.name + ": " + product.price.toFixed(2) + " EUR";
}
export function isCheap(product: Product): boolean {
return product.price < 5;
}
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 label(product: Product): string {
return product.name + ": " + product.price.toFixed(2) + " EUR";
}
export function isCheap(product: Product): boolean {
return product.price < 5;
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {label, isCheap} from './main.ts';
test('label shows name and price', () => {
assert.equal(label({name: 'Pen', price: 1.5}), 'Pen: 1.50 EUR', 'label should give Pen: 1.50 EUR');
});
test('isCheap is true below 5', () => {
assert.equal(isCheap({name: 'Pen', price: 1.5}), true, 'a price of 1.5 should be cheap');
});
test('isCheap is false at 5 and above', () => {
assert.equal(isCheap({name: 'Mug', price: 8}), false, 'a price of 8 should not be cheap');
});
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
Let Dog build on Animal
describe reads name and legs from a Dog, but Dog declares only breed: tsc reports Property 'name' does not exist on type 'Dog', and the same for legs. Do not copy the properties into Dog. Make Dog build on Animal instead, so it has every property of Animal plus breed.
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
The JavaScript runs fine: only the type-check fails, because Dog does not know about name and legs.
Hint 2
One keyword after interface Dog makes it take over all the members of another interface.
Hint 3
Write interface Dog extends Animal {
Show a solution
One way to solve it. Yours can look different and still pass the checks.
interface Animal {
name: string;
legs: number;
}
interface Dog extends Animal {
breed: string;
}
export function describe(dog: Dog): string {
return dog.name + " the " + dog.breed + " has " + dog.legs + " legs";
}
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
interface Animal {
name: string;
legs: number;
}
interface Dog {
breed: string;
}
export function describe(dog: Dog): string {
return dog.name + " the " + dog.breed + " has " + dog.legs + " legs";
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {describe} from './main.ts';
test('describe names the dog, its breed and its legs', () => {
assert.equal(describe({name: 'Rex', breed: 'Collie', legs: 4}), 'Rex the Collie has 4 legs', 'describe should give Rex the Collie has 4 legs');
});
test('describe works for another dog', () => {
assert.equal(describe({name: 'Tripod', breed: 'Beagle', legs: 3}), 'Tripod the Beagle has 3 legs', 'describe should give Tripod the Beagle has 3 legs');
});
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 = after interface
interface Point = {
x: number;
y: number;
};
What tsc or Node.js prints
main.ts(1,17): error TS1005: '{' expected.Why, and the fix
type and interface are written differently. A type alias needs =: type Point = { x: number; y: number };. An interface has no =, the braces follow the name: interface Point { x: number; y: number }. The message '{' expected points at the =, column 17, where tsc wanted the brace.
The same type alias declared twice
type Settings = { theme: string };
type Settings = { fontSize: number };
What tsc or Node.js prints
main.ts(1,6): error TS2300: Duplicate identifier 'Settings'.Why, and the fix
A type alias cannot be re-opened, so tsc reports both declarations (lines 1 and 2). Put all the properties into one: type Settings = { theme: string; fontSize: number };. Two interface Settings declarations would merge instead, which is allowed, but a second declaration far away in the file is hard to spot; keep one.
A property from the base interface left out
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const rex: Dog = { breed: "Collie" };
What tsc or Node.js prints
main.ts(9,7): error TS2741: Property 'name' is missing in type '{ breed: string; }' but required in type 'Dog'.Why, and the fix
Dog extends Animal, so a Dog needs every property of Animal as well as its own. The message names the missing one, name, and the type that requires it, Dog. Add it: { name: "Rex", breed: "Collie" }.
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.