TypeScript's built-in utility types transform existing types without rewriting them — Partial makes all fields optional, Pick selects a subset of fields, Omit removes fields, Record defines key-value maps, and Exclude/Extract manipulate union types — all resolved at compile time with zero runtime cost.
TypeScript ships with built-in utility types — Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract — that transform types without you writing the transformation from scratch every time.
Object utility types
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
address?: string;
}
// Partial<T> — all properties become optional
type UserUpdate = Partial<User>;
const update: UserUpdate = { name: "Priya" }; // all other fields omitted
// Required<T> — all optional properties become required
type FullUser = Required<User>; // address is now required
// Readonly<T> — all properties become readonly
type FrozenUser = Readonly<User>;
// frozenUser.name = "x"; // Error
// Pick<T, K> — keep only specified keys
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string }
// Omit<T, K> — remove specified keys
type UserWithoutRole = Omit<User, "role" | "id">;
// { name: string; email: string; address?: string }
// Record<K, V> — object type with keys K and values V
type RolePermissions = Record<"admin" | "user" | "guest", string[]>;
const perms: RolePermissions = {
admin: ["read","write","delete"],
user: ["read","write"],
guest: ["read"],
};
// Record for a lookup table
type ErrorMessages = Record<number, string>;
const httpErrors: ErrorMessages = { 404: "Not Found", 500: "Server Error" };Set and function utility types
// Union manipulation
type AllRoles = "admin" | "user" | "guest" | "moderator";
type PublicRoles = "user" | "guest";
// Exclude<T, U> — remove U from union T
type PrivateRoles = Exclude<AllRoles, PublicRoles>; // "admin" | "moderator"
// Extract<T, U> — keep only the members of T that are in U
type CommonRoles = Extract<AllRoles, "user" | "guest" | "unknown">; // "user" | "guest"
// NonNullable<T> — remove null and undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
// Function utility types
function fetchUser(id: number, token: string): Promise<User> { ... }
type Params = Parameters<typeof fetchUser>; // [number, string]
type Result = ReturnType<typeof fetchUser>; // Promise<User>
type Resolved = Awaited<ReturnType<typeof fetchUser>>; // User
// ConstructorParameters<T> — constructor argument types
class Logger {
constructor(private name: string, private level: number) {}
}
type LoggerArgs = ConstructorParameters<typeof Logger>; // [string, number]
// InstanceType<T> — the type an constructor produces
type LoggerInstance = InstanceType<typeof Logger>; // LoggerAdvanced patterns: satisfies, const, and template literals
// satisfies (TypeScript 4.9) — validate type without widening
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// Without satisfies: palette.red would be string | number[] (widened)
// With satisfies: palette.red is number[] (inferred precisely)
palette.red.map(v => v * 2); // OK — TypeScript knows it's number[]
// as const — make all values literal types, readonly recursively
const config = {
host: "localhost",
port: 3000,
debug: true,
} as const;
// config: { readonly host: "localhost"; readonly port: 3000; readonly debug: true }
// Combined with typeof for enums-without-enums
const Direction = {
NORTH: "north",
SOUTH: "south",
EAST: "east",
WEST: "west",
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
// "north" | "south" | "east" | "west"
// Template literal types for API path validation
type ApiPath = \`/api/\${string}\`;
function callApi(path: ApiPath) { ... }
callApi("/api/users"); // OK
// callApi("/users"); // Error — must start with /api/tsconfig.json — the critical settings
{
"compilerOptions": {
"target": "ES2022", // JS version to output
"module": "ESNext", // module system (ESNext = ES modules)
"lib": ["ES2022", "DOM"], // type definitions to include
"outDir": "./dist", // where to put compiled JS
"rootDir": "./src", // where source TS lives
// Strictness — always enable these
"strict": true, // enables all strict checks below
"noImplicitAny": true, // error on implicit any
"strictNullChecks": true, // null/undefined are not assignable to other types
"strictFunctionTypes": true, // correct function parameter variance
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined, not T
"noImplicitReturns": true, // all code paths must return
"noFallthroughCasesInSwitch": true, // switch cases must break/return
// Module resolution
"moduleResolution": "bundler", // for Vite/esbuild/Webpack 5
"resolveJsonModule": true,
"esModuleInterop": true,
"isolatedModules": true, // each file compilable independently (needed for esbuild)
// Paths
"paths": {
"@/*": ["./src/*"] // absolute imports like: import { x } from "@/utils"
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Declaration merging and module augmentation
// Add properties to an existing module's types
// Useful for adding types to third-party libraries
// Augment Express Request type
import "express";
declare module "express" {
interface Request {
user?: { id: number; role: string };
}
}
// Now req.user is typed everywhere you use Express
app.get("/profile", (req, res) => {
if (req.user) {
res.json({ id: req.user.id }); // type-safe
}
});
// Global augmentation
declare global {
interface Window {
analytics: { track(event: string): void };
}
}
window.analytics.track("page_view"); // type-safe
// Namespace merging — add to a namespace
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
namespace Validation {
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) { return /^[A-Za-z]+$/.test(s); }
}
}strict: true in tsconfig is NOT on by default. A fresh TypeScript project without "strict": true has very permissive type checking — implicit any is allowed, null checks are off. Always start with "strict": true. Enabling it on an existing codebase with loose types will produce many errors — this is correct, not a bug.Partial<T> makes all properties optional, including nested objects. Nested properties are NOT made partial — only the top-level properties are affected. For deep partial, you need a recursive type: type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }.as const makes values readonly and literal, but does not make objects immutable at runtime. At runtime, the object can still be mutated — as const is a TypeScript compile-time constraint only. It narrows the types to the exact literal values, preventing TypeScript from widening "north" to string.The TypeScript type system: soundness and intentional unsoundness
TypeScript is intentionally not fully sound — it makes pragmatic trade-offs. Known unsoundnesses: (1) function parameter bivariance for methods (for compatibility with common OOP patterns); (2) type assertions (as T) bypass the type checker; (3) any is a hole in the type system; (4) object literals have excess property checking but objects in variables do not; (5) noUncheckedIndexedAccess is not in strict and must be enabled separately. The TypeScript team explicitly documents these trade-offs and refers to them as "unsoundness for practicality."
TypeScript 5.x — new features
TypeScript 5.0 (March 2023) introduced: decorator metadata (aligning with the TC39 decorators proposal Stage 3), const type parameters, variadic tuple improvements, and --verbatimModuleSyntax for cleaner ESM output. TypeScript 5.2 added using declarations (using / await using) implementing the TC39 Explicit Resource Management proposal — the TypeScript equivalent of Java try-with-resources, ensuring disposal even on error. TypeScript 5.5 (June 2024) added inferred type predicates — the compiler can automatically infer user-defined type guards from function bodies without requiring explicit value is T annotations.