TypeScript basics: types, interfaces, unions and narrowing
TypeScript adds type annotations to JavaScript and checks them before your code runs. These are the core ideas you need to read and write everyday TypeScript.
Not checked by a trusted editor yet Last edited Outdated?
TypeScript is JavaScript with types. You describe what shape your data has, and the compiler (tsc) or your editor warns you when code does not match, such as calling a method that might not exist. The types are removed before the code runs, so they cost nothing at runtime and also do not check anything at runtime.
Annotations and inference
You annotate function parameters and public APIs. Inside functions, TypeScript usually works out the types on its own:
function greet(name: string, times: number = 1): string {
return `Hello, ${name}! `.repeat(times);
}
const count = 3; // inferred as the literal type 3
let title = "Draft"; // inferred as string
const tags: string[] = []; // annotate empty arrays
Object types with interface and type
interface User {
id: string | number;
name: string;
email?: string; // optional: string | undefined
readonly createdAt: Date; // cannot be reassigned
}
type Point = { x: number; y: number };
interface and type overlap a lot for object shapes. A common convention: use interface for object shapes that others may extend, and type for unions, tuples and computed types.
Union types
A union says a value can be one of several types: string | number, or "light" | "dark" for a fixed set of strings. You can only use what all members have in common until you narrow it.
Narrowing
Narrowing is how you go from a broad type to a specific one with ordinary JavaScript checks. TypeScript follows your if statements:
function formatId(id: string | number): string {
if (typeof id === "number") {
return id.toString().padStart(6, "0"); // id is number here
}
return id.toUpperCase(); // id is string here
}
Checks that narrow include typeof, instanceof, "key" in obj, equality checks, truthiness checks and Array.isArray.
Discriminated unions
Give each member of a union a shared literal field, and a switch on that field narrows perfectly. Assigning to never in the default branch makes the compiler complain if you add a new shape and forget to handle it:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
default: {
const unreachable: never = shape;
throw new Error(`Unknown shape: ${JSON.stringify(unreachable)}`);
}
}
}
unknown, any and type guards
Data from JSON.parse, fetch or user input has no guaranteed shape. Type it as unknown, which forces you to check before use, rather than any, which turns checking off. A function returning value is User is a type guard that narrows wherever it returns true:
function isUser(value: unknown): value is User {
return typeof value === "object" && value !== null && "id" in value && "name" in value;
}
const data: unknown = JSON.parse(text);
if (isUser(data)) console.log(data.name);
For larger payloads, a validation library such as Zod or Valibot checks the data at runtime and produces the TypeScript type for you.
Generics and utility types
Generics let one function work with many types while keeping them connected: function first<T>(items: readonly T[]): T | undefined returns a number for a number array and a User for a user array. Built-in helpers transform types: Partial<User>, Pick<User, "id" | "name">, Omit, Record<string, number> and ReturnType.
Getting started
- Turn on
"strict": trueintsconfig.jsonfrom day one; adding it later is much harder. - Run
npx tsc --noEmitto type-check without producing files. - Recent Node versions can run
.tsfiles directly by stripping the types (node app.ts). That skips type checking, so keeptscin your editor and CI.
Repos in the wild
- TypeScript microsoft/TypeScript Microsoft's TypeScript language and compiler: typed JavaScript that compiles to plain JavaScript, plus the language service that powers editor features. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Commitpedia +3,999 chars