// Interface — extendable, good for objects and classesinterface User { id: number; name: string; email?: string; // optional}// Extend an interfaceinterface AdminUser extends User { role: "admin" | "superadmin";}// Type alias — more flexible, good for unions and computed typestype Status = "active" | "inactive" | "pending";type ID = string | number;type UserOrAdmin = User | AdminUser;// When to use which:// - Interface: when defining object shapes, especially for classes// - Type alias: for unions, primitives, computed types, or when you need `Exclude`/`Extract`
Generics in practice
// Generic functionfunction first<T>(arr: T[]): T | undefined { return arr[0];}const n = first([1, 2, 3]); // type: number | undefinedconst s = first(["a", "b"]); // type: string | undefined// Generic with constraintfunction getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}const user = { name: "Alice", age: 30 };getProperty(user, "name"); // stringgetProperty(user, "age"); // number// getProperty(user, "email"); // Error: not a key of user// Generic interfaceinterface ApiResponse<T> { data: T; status: number; message: string;}type UserResponse = ApiResponse<User>;type ListResponse = ApiResponse<User[]>;
Utility types — real examples
interface User { id: number; name: string; email: string; password: string; role: "user" | "admin";}// Partial — all optional (useful for update functions)function updateUser(id: number, data: Partial<User>) { ... }updateUser(1, { name: "Alice" }); // only name, no other fields required// Pick — keep only what you need (useful for API responses)type PublicUser = Pick<User, "id" | "name" | "role">;// { id: number; name: string; role: "user" | "admin" }// Omit — remove sensitive fieldstype SafeUser = Omit<User, "password">;// Record — typed dictionary/maptype RolePermissions = Record<User["role"], string[]>;const perms: RolePermissions = { user: ["read"], admin: ["read", "write", "delete"],};// ReturnType — get what a function returnsasync function fetchUser(id: number) { return { id, name: "Alice", role: "user" as const };}type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;// { id: number; name: string; role: "user" }
Type guards
// typeof guardfunction format(value: string | number): string { if (typeof value === "string") { return value.toUpperCase(); // TypeScript knows it's string here } return value.toFixed(2); // TypeScript knows it's number here}// instanceof guardfunction processDate(value: Date | string): Date { if (value instanceof Date) { return value; } return new Date(value);}// Custom type guard (predicate function)interface Cat { meow(): void }interface Dog { bark(): void }function isCat(pet: Cat | Dog): pet is Cat { return (pet as Cat).meow !== undefined;}function makeSound(pet: Cat | Dog) { if (isCat(pet)) { pet.meow(); // TypeScript knows it's Cat } else { pet.bark(); // TypeScript knows it's Dog }}// Discriminated union — the cleanest patterntype Shape = | { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number } | { kind: "triangle"; base: number; height: number };function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "rectangle": return shape.width * shape.height; case "triangle": return 0.5 * shape.base * shape.height; }}
as const — immutable literal types
// Without as const — type is string[]const directions = ["north", "south", "east", "west"];// type: string[]// With as const — type is readonly tuple of literalsconst directions = ["north", "south", "east", "west"] as const;// type: readonly ["north", "south", "east", "west"]type Direction = typeof directions[number];// type: "north" | "south" | "east" | "west"// Great for config objectsconst config = { env: "production", version: 3, features: ["auth", "analytics"],} as const;type Env = typeof config.env; // "production" (not string)
Mapped types
// Make all properties nullabletype Nullable<T> = { [K in keyof T]: T[K] | null };// Make all properties async getterstype AsyncGetters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => Promise<T[K]>;};// Filter properties by value typetype OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K];};interface Mixed { name: string; age: number; email: string; active: boolean }type StringFields = OnlyStrings<Mixed>;// { name: string; email: string }