Irbisa · Cheatsheet
OOP, SOLID & Design Patterns
Classes and OOP in TypeScript, SOLID, DRY, KISS and YAGNI, coupling and cohesion, GoF patterns in frontend code: observer, module, factory, strategy, adapter, facade, proxy, anti-patterns
What are the four principles of OOP, and how does each one look in TypeScript?
// abstraction: callers see what a payment method does, not howinterface PaymentMethod { pay(amountCents: number): Promise<string>;} // inheritance: keeps message and stack trace, adds a status code…What is the difference between
privateand#privatein a TypeScript class, and what doprotectedandreadonlyadd?class Account { private pin = '1234'; // checked by tsc only, a normal property at runtime #token = 'secret'; // a JavaScript private field, enforced by the engine protected readonly owner: string; constructor(owner: string) {…When do you use an abstract class and when an interface in TypeScript?
// the contract callers depend oninterface Uploader { upload(file: Blob): Promise<string>;} // shared algorithm; subclasses fill in the steps marked abstract…What do DRY, KISS and YAGNI mean, and when does removing duplication make frontend code worse?
What do low coupling and high cohesion mean in a frontend codebase, and how do you spot code that has neither?
Why is composition usually preferred over inheritance in TypeScript code, and when is extending a class still the right choice?
class TagList { protected items: string[] = []; add(tag: string) { this.items.push(tag); } addAll(tags: string[]) {…What are the five SOLID principles, and what does each look like in plain TypeScript: services, API clients and validators?
// O: each rule is a value; a new rule is a new function, not another `if` in validate()type Rule = (value: string) => string | null; const required: Rule = (v) => (v.trim() ? null : 'Required');const minLength = (n: number): Rule => (v) => (v.length >= n ? null : `At least ${n} characters`);const email: Rule = (v) => (/^\S+@\S+\.\S+$/.test(v) ? null : 'Not an email');…GuestCartextendsCartand throws fromcheckout(). Which SOLID principle does it break, and how would you fix the design?class Cart { protected items: CartItem[] = []; add(item: CartItem) { this.items.push(item); } checkout(api: OrdersApi): Promise<string> { return api.createOrder(this.items); }} // guests can fill a cart but must sign in to payclass GuestCart extends Cart { override async checkout(): Promise<string> { throw new Error('Sign in to check out'); }} async function buyNow(cart: Cart, item: CartItem, api: OrdersApi) { cart.add(item); return cart.checkout(api);}What are the Module and Singleton patterns, and why is an ES module already a singleton?
// before ES modules: an IIFE kept its variables private (the Module pattern)var legacyCounter = (function () { var count = 0; return { increment: function () { return ++count; } };})();…What is the Factory pattern, and how would you use a factory to create the right API client for each environment?
export interface ApiClient { get<T>(path: string): Promise<T>;} function httpClient(baseUrl: string): ApiClient { return {…What is the difference between the Adapter and Facade patterns, and where do you use each in a frontend app?
// what the backend sendsinterface UserDto { user_id: number; full_name: string; created_at: string; // ISO date}…What is the Proxy pattern, and how is it different from the JavaScript
Proxyobject?interface ExchangeRates { rate(from: string, to: string): Promise<number>;} class HttpExchangeRates implements ExchangeRates { async rate(from: string, to: string) {…What is the Decorator pattern, and how do wrapper functions and TypeScript decorators such as
@loggedimplement it?// a wrapper function: the Decorator pattern for a plain functionfunction withTiming<A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) { return async (...args: A): Promise<R> => { const start = performance.now(); try { return await fn(...args);…What is the difference between the Observer pattern and publish/subscribe, and where do you meet each in frontend code?
import { useSyncExternalStore } from 'react'; // Observer: the subject keeps its own listeners and calls them itselffunction createCounterStore() { let count = 0; const listeners = new Set<() => void>();…Find the bug:
destroy()callsoff, yet every closed search panel keeps re-rendering on each store change.class SearchPanel { constructor(readonly store: SearchStore, readonly el: HTMLElement) { store.on('change', this.render.bind(this)); } render() { this.el.textContent = `${this.store.results.length} results`; } destroy() { this.store.off('change', this.render.bind(this)); this.el.remove(); }}What is the Mediator pattern, and how is it different from Observer?
type FieldName = 'country' | 'delivery' | 'isCompany' | 'vatId'; interface FieldState { value: string; disabled: boolean; options?: string[];…What are the Strategy and Command patterns, and why are both often just functions or plain objects in TypeScript?
// Strategy: how shipping is priced, picked by keytype ShippingStrategy = (weightKg: number) => number; const shipping = { standard: (kg) => 500 + kg * 100, express: (kg) => 1500 + kg * 250,…Which classic design patterns does JavaScript make trivial with first-class functions, closures and modules, and which ones still need design?
How do you apply dependency inversion to a frontend's API layer (ports and adapters), and when is it not worth it?
// orders/model.ts: the port, in the domain's own termsexport interface Order { id: string; totalCents: number; placedAt: Date;}…Which anti-patterns do you meet most in frontend codebases (a god store, a
utilsdumping ground, the wrong abstraction), and how do you refactor out of them?