Design Patterns in TypeScript
TypeScript's type system makes some design patterns more powerful than in plain JavaScript. Type guards narrow types, the builder pattern creates fluent APIs, and discriminated unions enable exhaustive pattern matching.
40 min•By Priygop Team•Last updated: Feb 2026
TypeScript Design Patterns
Example
// Type guard functions
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value
);
}
// Exhaustive switch with never
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "rect": return shape.width * shape.height;
case "triangle": return 0.5 * shape.base * shape.height;
default: {
const _exhaustive: never = shape; // Error if a case is missed!
return _exhaustive;
}
}
}
// Builder pattern
class QueryBuilder<T> {
private filters: string[] = [];
private sortField?: keyof T;
private limitCount?: number;
where(field: keyof T, value: string): this {
this.filters.push(`${String(field)} = '${value}'`);
return this; // Return 'this' for chaining
}
orderBy(field: keyof T): this {
this.sortField = field;
return this;
}
limit(count: number): this {
this.limitCount = count;
return this;
}
build(): string {
let query = "SELECT * FROM table";
if (this.filters.length) query += " WHERE " + this.filters.join(" AND ");
if (this.sortField) query += ` ORDER BY ${String(this.sortField)}`;
if (this.limitCount) query += ` LIMIT ${this.limitCount}`;
return query;
}
}
// Usage with type safety
interface Product { name: string; price: number; category: string; }
const query = new QueryBuilder<Product>()
.where("category", "electronics")
.orderBy("price")
.limit(10)
.build();
// Singleton pattern
class Database {
private static instance: Database;
private constructor() {} // Private constructor prevents new Database()
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
}