TL;DR
- TypeScript = JavaScript + static types. Types are erased at runtime.
- Use
interface for object shapes, type for unions/intersections/utilities.
unknown > any. Always narrow before using.
Basic Types
// Primitives
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
// Arrays
let ids: number[] = [1, 2, 3];
let names: Array<string> = ["a", "b"];
// Tuples
let pair: [string, number] = ["age", 30];
// Objects
let user: { name: string; age: number } = { name: "Alice", age: 30 };
Interfaces vs Types
// Interface — extendable, for object shapes
interface User {
id: number;
name: string;
email?: string; // optional
}
interface Admin extends User {
permissions: string[];
}
// Type — for unions, intersections, utilities
type Status = "active" | "inactive" | "banned";
type Result = Success | Failure;
type Readonly<T> = { readonly [K in keyof T]: T[K] };
Utility Types
| Utility |
What it does |
Partial<T> |
All props optional |
Required<T> |
All props required |
Pick<T, K> |
Only specified keys |
Omit<T, K> |
Exclude specified keys |
Record<K, V> |
Object with keys K, values V |
ReturnType<F> |
Infer return type of function |
Awaited<T> |
Unwrap Promise type |
Narrowing
function handle(input: string | number) {
if (typeof input === "string") {
return input.toUpperCase(); // TS knows it's string
}
return input * 2; // TS knows it's number
}
// Discriminated unions
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rect": return s.width * s.height;
}
}
Generics
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// Constrained generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}