Made by VeraStack Labs
Contents
This page is for later. You have been through the lessons, and now you want the answer to "how do I write that again?" in ten seconds.
Each row shows the JavaScript you know on the left and the TypeScript it becomes on the right, with the lines that differ tinted. Every row is real code that was type-checked and tested, like the lessons. Each row names the lesson that explains it, so you can go back for the reasons.
Two lessons have no rows, because they have no code to compare. Lesson 0 is the honest list of what you gain and what it costs. Lesson 1 is setup. These are the commands from it.
| You want to | Type this |
|---|---|
| Add TypeScript to a project | npm i -D typescript |
Make a tsconfig.json | npx tsc --init |
| Check without producing files | npx tsc --noEmit |
Run a .ts file with Node's type stripping | node file.ts |
Use the contents list to jump between groups. To keep a copy, print the page or save it as a PDF. The header, the sidebar and the buttons are left out of the printout.
Start here
Setup, labels, and letting TypeScript work it out.
Label a variable
Lesson 2: Your first typesconst city = "Pune";let seats = 40;let isOpen = true;const city: string = "Pune";let seats: number = 40;let isOpen: boolean = true;A colon and a type. You rarely need to, because TypeScript works the type out from the value.
Let inference do it
Lesson 2: Your first typesconst price = 10;const tag = "Rs " + price;const price = 10; // numberconst tag = "Rs " + price; // stringNo label needed when the value says it all. The comments show what TypeScript worked out.
A variable keeps its type
Lesson 2: Your first typeslet age = 21;age = "twenty-one";console.log(age + 1);let age = 21;age = "twenty-one";console.log(age + 1);TypeScript says
error TS2322: Type 'string' is not assignable to type 'number'.
The first value decides the type, so a later value of another kind is refused.
Objects, arrays, tuples
The shape of your data, written once.
Name an object's shape
Lesson 3: Objectsconst user = { name: "Asha", age: 21 };type User = { name: string; age: number };const user: User = { name: "Asha", age: 21 };A type lists the properties and their kinds. An interface works too.
Optional and read-only
Lesson 3: Objectsconst contact = { id: 1 };type Contact = { readonly id: number; email?: string };const contact: Contact = { id: 1 };? means may be missing. readonly means set once, never changed.
Changing a read-only property
Lesson 3: Objectsconst config = { host: "localhost", port: 8080 };config.port = 9090;console.log(config.port);type Config = { readonly host: string; readonly port: number;};const config: Config = { host: "localhost", port: 8080 };config.port = 9090;console.log(config.port);TypeScript says
error TS2540: Cannot assign to 'port' because it is a read-only property.
The checker refuses the assignment. It is a promise to your teammates, and it is gone when the code runs.
A list of one kind
Lesson 4: Arrays, tuples and functionsconst scores = [90, 85];scores.push(77);const scores: number[] = [90, 85];scores.push(77);number[], or Array<number>. Pushing the wrong kind is refused.
A fixed list, by position
Lesson 4: Arrays, tuples and functionsconst ticket = ["B2", 14];const [coach, seat] = ticket;const ticket: [coach: string, seat: number] = ["B2", 14];const [coach, seat] = ticket;Each position has its own type, and names make it readable.
Functions, labelled
Inputs, outputs, defaults and callbacks.
Label inputs and the result
Lesson 4: Arrays, tuples and functionsfunction total(prices) { return prices.reduce((sum, price) => sum + price, 0);}function total(prices: number[]): number { return prices.reduce((sum, price) => sum + price, 0);}Label the parameters. The return type can be left to inference, but many teams write it for public functions.
Default and optional parameters
Lesson 4: Arrays, tuples and functionsfunction greet(person, greeting = "Hello", suffix) { return greeting + ", " + person + (suffix ?? "");}function greet(person: string, greeting = "Hello", suffix?: string): string { return greeting + ", " + person + (suffix ?? "");}A default value gives the type for free. A ? parameter may be undefined inside.
Describe a function
Lesson 4: Arrays, tuples and functionsconst apply = (n, change) => change(n);type Change = (n: number) => number;const apply = (n: number, change: Change): number => change(n);(n: number) => number reads: takes a number, gives a number. Name it with type to reuse it.
Unions and narrowing
One of several, and how to find out which.
Text or a number
Lesson 5: Unions and narrowingfunction label(id) { return typeof id === "string" ? id.toUpperCase() : "No. " + id;}function label(id: string | number): string { return typeof id === "string" ? id.toUpperCase() : "No. " + id;}A | B means one of them. Check with typeof before using what only one has.
Has this property?
Lesson 5: Unions and narrowingfunction score(player) { return "runs" in player ? player.runs : player.wickets;}type Batter = { runs: number };type Bowler = { wickets: number };function score(player: Batter | Bowler): number { return "runs" in player ? player.runs : player.wickets;}The in operator tells TypeScript which member of the union you hold.
A kind field
Lesson 5: Unions and narrowingfunction serve(order) { switch (order.kind) { case "chai": return order.sugar + " sugars"; case "coffee": return order.strong ? "strong" : "mild"; }}type Order = { kind: "chai"; sugar: number } | { kind: "coffee"; strong: boolean };function serve(order: Order): string { switch (order.kind) { case "chai": return order.sugar + " sugars"; case "coffee": return order.strong ? "strong" : "mild"; }}Give every member a kind with an exact word. A switch on it narrows to one member.
Check every case was handled
Lesson 5: Unions and narrowingfunction price(drink) { switch (drink) { case "chai": return 10; case "coffee": return 20; default: return undefined; }}type Drink = "chai" | "coffee";function assertNever(value: never): never { throw new Error("Unhandled: " + value);}function price(drink: Drink): number { switch (drink) { case "chai": return 10; case "coffee": return 20; default: return assertNever(drink); }}In default, pass the value to a function that takes never. A missing case becomes an error.
A few exact words
Lesson 5: Unions and narrowingfunction brew(strength) { return strength === "strong" ? "Two shots" : "One shot";}type Strength = "mild" | "strong";function brew(strength: Strength): string { return strength === "strong" ? "Two shots" : "One shot";}A union of words works like a small enum, with nothing added to your JavaScript.
A word that is not on the list
Lesson 5: Unions and narrowingfunction brew(strength) { return strength === "strong" ? "Two shots" : "One shot";}console.log(brew("strong"));console.log(brew("medium"));type Strength = "mild" | "strong";function brew(strength: Strength): string { return strength === "strong" ? "Two shots" : "One shot";}console.log(brew("strong"));console.log(brew("medium"));TypeScript says
error TS2345: Argument of type '"medium"' is not assignable to parameter of type 'Strength'.
The editor offers the allowed words as you type, and refuses the rest.
Missing values
null and undefined, handled before they bite.
Check what find returns
Lesson 6: null, undefined and strictnessfunction nameOf(students, roll) { const found = students.find((s) => s.roll === roll); return found === undefined ? "No such student" : found.name;}type Student = { roll: number; name: string };function nameOf(students: Student[], roll: number): string { const found = students.find((s) => s.roll === roll); return found === undefined ? "No such student" : found.name;}find gives the item or undefined. Look first, then use.
Reaching into a maybe
Lesson 6: null, undefined and strictnessconst students = [{ roll: 1, name: "Asha" }];function nameOf(roll) { return students.find((s) => s.roll === roll)?.name;}console.log(nameOf(1));console.log(nameOf(9));type Student = { roll: number; name: string };const students: Student[] = [ { roll: 1, name: "Asha" }, { roll: 2, name: "Ravi" },];function nameOf(roll: number): string { return students.find((s) => s.roll === roll).name;}console.log(nameOf(1));console.log(nameOf(9));TypeScript says
error TS2532: Object is possibly 'undefined'.
The checker stops you at the dot, where JavaScript crashes later.
Stop at the first empty box
Lesson 6: null, undefined and strictnessfunction cityOf(customer) { return customer.address?.city;}type Customer = { address?: { city: string } };function cityOf(customer: Customer): string | undefined { return customer.address?.city;}?. gives undefined instead of crashing. The result type says it may be missing.
Fallback for missing, not for zero
Lesson 6: null, undefined and strictnessfunction runsLabel(runs) { return "Runs: " + (runs || "did not bat");}function runsLabel(runs: number | undefined): string { return "Runs: " + (runs ?? "did not bat");}?? replaces only null and undefined. || also replaces 0, empty text and false.
The bang, with a proof
Lesson 6: null, undefined and strictnessconst seats = new Map([["A", 1]]);const next = seats.get("A") + 1;const seats = new Map<string, number>([["A", 1]]);// Only when the line above proves it is there.const next = seats.get("A")! + 1;! is a promise. Use it only when the line above proves the value is there.
Generics, just enough
One function for many types.
A blank type
Lesson 7: Generics, just enoughfunction first(items) { return items[0];}function first<T>(items: T[]): T | undefined { return items[0];}<T> is a blank filled in from the arguments, so the output keeps the input's type.
Limit the blank
Lesson 7: Generics, just enoughfunction longest(a, b) { return a.length >= b.length ? a : b;}console.log(longest("chai", "coffee"));console.log(longest(10, 20));function longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b;}console.log(longest("chai", "coffee"));console.log(longest(10, 20));TypeScript says
error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
T extends { length: number } means any type that has a length.
A type with a blank
Lesson 7: Generics, just enoughconst lucky = { value: 7 };const note = { value: "Call Amma" };type Box<T> = { value: T };const lucky: Box<number> = { value: 7 };const note: Box<string> = { value: "Call Amma" };Box<number> fills the blank. Array<T>, Map<K, V> and Promise<T> work the same way.
Utility types
Reshape a type you already have.
Partial: every property optional
Lesson 8: Utility typesfunction updateUser(user, changes) { return { ...user, ...changes };}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };const renamed = updateUser(asha, { nmae: "Asha Rao" });console.log(renamed.name);type User = { id: number; name: string; email: string; password: string };function updateUser(user: User, changes: Partial<User>): User { return { ...user, ...changes };}const asha: User = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };const renamed = updateUser(asha, { nmae: "Asha Rao" });console.log(renamed.name);TypeScript says
error TS2353: Object literal may only specify known properties, and 'nmae' does not exist in type 'Partial<User>'.
Good for update functions. A misspelled key is refused.
Pick and Omit
Lesson 8: Utility typesconst user = { id: 1, name: "Asha", password: "chai123" };const preview = { id: user.id, name: user.name };const { password, ...publicUser } = user;type User = { id: number; name: string; password: string };type Preview = Pick<User, "id" | "name">;type PublicUser = Omit<User, "password">;Pick keeps the named properties. Omit removes them, and does not check the spelling of its keys.
Record: a value for every key
Lesson 8: Utility typesconst prices = { chai: 10, coffee: 20 };type Drink = "chai" | "coffee";const prices: Record<Drink, number> = { chai: 10, coffee: 20 };With a union of words as the key, none may be forgotten.
ReturnType: borrow a result type
Lesson 8: Utility typesfunction makeTicket(seat) { return { seat, paid: false };}function makeTicket(seat: number) { return { seat, paid: false };}type Ticket = ReturnType<typeof makeTicket>;typeof in a type position means the type of that value. Also see Required and Readonly.
Data from outside
Check the parcel at the door.
unknown must be checked
Lesson 9: Data from outsidefunction shout(value) { return value.toUpperCase();}console.log(shout("chai"));function shout(value: unknown): string { return value.toUpperCase();}TypeScript says
error TS18046: 'value' is of type 'unknown'.
any lets everything through. unknown waits for a check.
Narrow an unknown
Lesson 9: Data from outsidefunction describe(value) { return typeof value === "string" ? value.toUpperCase() : "not text";}function describe(value: unknown): string { return typeof value === "string" ? value.toUpperCase() : "not text";}A typeof check turns unknown into something you can use.
A type guard
Lesson 9: Data from outsidefunction isTicket(value) { return ( typeof value === "object" && value !== null && typeof value.coach === "string" && typeof value.seat === "number" );}type Ticket = { coach: string; seat: number };function isTicket(value: unknown): value is Ticket { return ( typeof value === "object" && value !== null && "coach" in value && typeof value.coach === "string" && "seat" in value && typeof value.seat === "number" );}value is Ticket tells TypeScript what a true answer means. A guard that lies is worse than none.
Check what fetch gives back
Lesson 9: Data from outsideasync function loadScores(url) { const response = await fetch(url); return response.json();}function isNumberList(value: unknown): value is number[] { return Array.isArray(value) && value.every((item) => typeof item === "number");}async function loadScores(url: string): Promise<number[]> { const response = await fetch(url); const data: unknown = await response.json(); return isNumberList(data) ? data : [];}response.json() is any. Put it in unknown and check it before use.
Migrating, step by step
Let it in, check a little, convert a file.
Rename a file, add types
Lesson 10: Migrating a real projectfunction lineTotal(price, quantity) { return price * quantity;}function lineTotal(price: number, quantity: number): number { return price * quantity;}Rename .js to .ts, then label the parameters. Climb the strictness ladder one rung at a time.
A known gap, written down
Lesson 10: Migrating a real projectfunction legacyTotal(prices) { return prices.reduce((sum, price) => sum + Number(price), 0);}legacyTotal("10,20".split(","));function legacyTotal(prices: number[]): number { return prices.reduce((sum, price) => sum + Number(price), 0);}// @ts-expect-error an old caller passes text. Fix it in the next change.legacyTotal("10,20".split(","));@ts-expect-error says the next line is known to fail, and complains when it stops failing.
Escape hatches
Passes that skip the checker, not the crash.
as const: exact and fixed
Lesson 11: Traps and escape hatchesconst order = { kind: "chai", sugar: 2 };const order = { kind: "chai", sugar: 2 } as const;Keeps the exact words and makes the properties read-only. It tells the checker more, not less.
satisfies: check, keep the keys
Lesson 11: Traps and escape hatchesconst prices = { chai: 10, coffee: 20 };type Prices = Record<string, number>;const prices = { chai: 10, coffee: 20 } satisfies Prices;Checks a value against a type without widening it.
A key that is not there
Lesson 11: Traps and escape hatchesconst prices = { chai: 10, coffee: 20 };console.log(prices.chai);console.log(prices.lassi);type Prices = Record<string, number>;const prices = { chai: 10, coffee: 20 } satisfies Prices;console.log(prices.chai);console.log(prices.lassi);TypeScript says
error TS2339: Property 'lassi' does not exist on type '{ chai: number; coffee: number; }'.
The exact keys are remembered, so a missing one is an error.
A union, not an enum
Lesson 11: Traps and escape hatchesconst sizes = ["small", "large"];type Size = "small" | "large";const sizes: Size[] = ["small", "large"];An enum becomes real JavaScript and Node cannot strip it. A union is only a type.
Check instead of cast
Lesson 11: Traps and escape hatchesconst data = "12";const text = typeof data === "string" ? data : "";const price = Number(text);const data: unknown = "12";const text = typeof data === "string" ? data : "";const price = Number(text);A check proves the kind. A cast only claims it.
A cast between strangers
Lesson 11: Traps and escape hatchesconst price = "12";const price = "12" as number;TypeScript says
error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Even as is refused between unrelated types. Going through unknown forges the pass, so avoid it.
Updated 2026-10-17
Made by VeraStack Labs