Patterns#
Patterns that recur in TypeScript codebases.
Strict Mode#
Turn on "strict": true in tsconfig.json. It enables a set of flags
including noImplicitAny, strictNullChecks, and
strictFunctionTypes that catch the majority of common bugs at compile
time.
Discriminated Unions#
Model variants with a shared literal tag and switch over it. The compiler
narrows each branch and warns on missing cases.
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
}
}
Branded Types#
A trick for nominally distinct types over the same underlying primitive.
type Brand<T, B> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type Email = Brand<string, "Email">;
Avoid any#
Prefer unknown when a value’s structure isn’t known. unknown forces a
narrowing check before use, where any silently disables type checking.
Schema-First Validation#
At system boundaries (HTTP, env vars, files) parse with a schema library so the rest of the program can rely on types.
import { z } from "zod";
const Config = z.object({
port: z.number().int().positive(),
name: z.string().min(1),
});
type Config = z.infer<typeof Config>;
const cfg: Config = Config.parse(loadEnv());
Readonly by Default#
Use readonly and ReadonlyArray<T> (or readonly T[]) to express
that a value isn’t expected to change.
function sum(xs: readonly number[]): number {
return xs.reduce((a, b) => a + b, 0);
}