TypeScript
Utility & Advanced Types
TypeScript ships utility types — built-in generics that transform existing types so you don't have to rewrite variants by hand.
The essential utility types
interface User {
id: number;
name: string;
email: string;
}
// Partial<T> — all properties optional (great for updates)
function updateUser(id: number, changes: Partial<User>) { /* ... */ }
updateUser(1, { name: "New Name" }); // ✅ only some fields
// Pick<T, K> — keep only selected keys
type UserPreview = Pick<User, "id" | "name">;
// Omit<T, K> — remove selected keys
type UserWithoutEmail = Omit<User, "email">;
// Required<T> — all properties required
// Readonly<T> — all properties readonly
type LockedUser = Readonly<User>;
// Record<K, V> — object with specific keys and value type
type RolePermissions = Record<"admin" | "user", string[]>;
💡
Partial is perfect for PATCH/update functions; Pick/Omit for deriving DTOs and view models from a base type.
Type narrowing with type guards
TypeScript narrows a broad type to a specific one inside a conditional. Built-in guards:
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // value is string here
}
return value.toFixed(2); // value is number here
}
Custom type guards use a value is Type return signature:
function isUser(v: unknown): v is User {
return typeof v === "object" && v !== null && "id" in v;
}
if (isUser(data)) {
console.log(data.name); // narrowed to User
}
Discriminated unions
A union of objects sharing a literal "tag" property — the idiomatic way to model variant data and state:
type Result =
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; message: string };
function render(r: Result) {
switch (r.status) {
case "loading": return "Loading…";
case "success": return r.data.name; // narrowed
case "error": return r.message; // narrowed
}
}
Watch & Learn
A recommended video to watch alongside this chapter.
More “Utility & Advanced Types” videos on YouTube