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

Middle Developer · 20 questions

  1. 01

    What are the four principles of OOP, and how does each one look in TypeScript?

    Encapsulation hides an object's state behind its methods, abstraction shows what an object does and not how, inheritance lets a subclass reuse a class's code, and polymorphism lets one call work with any object of the right type.

    // 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…
  2. 02

    What is the difference between private and #private in a TypeScript class, and what do protected and readonly add?

    private is a TypeScript check that disappears after compilation, so at runtime the field is an ordinary property.

    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) {…
  3. 03

    When do you use an abstract class and when an interface in TypeScript?

    An interface is only a contract: a shape with no code that disappears after compilation.

    // the contract callers depend oninterface Uploader {  upload(file: Blob): Promise<string>;} // shared algorithm; subclasses fill in the steps marked abstract…
  4. 04

    What do DRY, KISS and YAGNI mean, and when does removing duplication make frontend code worse?

    DRY (don't repeat yourself) says each piece of knowledge lives in one place, KISS (keep it simple) says pick the simplest design that works, and YAGNI (you aren't gonna need it) says do not build for needs you only imagine.

  5. 05

    What do low coupling and high cohesion mean in a frontend codebase, and how do you spot code that has neither?

    Coupling is how much a module depends on the details of other modules; cohesion is how closely the code inside one module belongs together.

  6. 06

    Why is composition usually preferred over inheritance in TypeScript code, and when is extending a class still the right choice?

    Inheritance ties a subclass to its parent's internals, so a change in the base class can break subclasses it has never seen, and a hierarchy allows one axis of variation.

    class TagList {  protected items: string[] = [];  add(tag: string) {    this.items.push(tag);  }  addAll(tags: string[]) {…
  7. 07

    What are the five SOLID principles, and what does each look like in plain TypeScript: services, API clients and validators?

    SOLID is five design rules: single responsibility, open/closed, Liskov substitution, interface segregation and dependency inversion.

    // 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');…
  8. 08

    GuestCart extends Cart and throws from checkout(). 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);}

    It breaks the Liskov substitution principle: buyNow is written for any Cart and expects checkout() to create an order, while a GuestCart compiles and then throws.

  9. 09

    What are the Module and Singleton patterns, and why is an ES module already a singleton?

    The Module pattern keeps state private in a closure and exposes a public API; Singleton ensures one instance with one global access point.

    // before ES modules: an IIFE kept its variables private (the Module pattern)var legacyCounter = (function () {  var count = 0;  return { increment: function () { return ++count; } };})();…
  10. 10

    What is the Factory pattern, and how would you use a factory to create the right API client for each environment?

    A factory is a function or method that creates objects and returns them as an interface, so the caller never names a concrete class.

    export interface ApiClient {  get<T>(path: string): Promise<T>;} function httpClient(baseUrl: string): ApiClient {  return {…
  11. 11

    What is the difference between the Adapter and Facade patterns, and where do you use each in a frontend app?

    An adapter converts one interface into the one your code expects, such as a backend DTO or an SDK into your own types.

    // what the backend sendsinterface UserDto {  user_id: number;  full_name: string;  created_at: string; // ISO date}…
  12. 12

    What is the Proxy pattern, and how is it different from the JavaScript Proxy object?

    A proxy is a stand-in with the same interface as the real object that controls access to it: it creates the object lazily, caches results, checks permissions or forwards calls.

    interface ExchangeRates {  rate(from: string, to: string): Promise<number>;} class HttpExchangeRates implements ExchangeRates {  async rate(from: string, to: string) {…
  13. 13

    What is the Decorator pattern, and how do wrapper functions and TypeScript decorators such as @logged implement it?

    A decorator wraps an object or function in another with the same interface and adds behaviour around the call: logging, timing, caching, retries.

    // 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);…
  14. 14

    What is the difference between the Observer pattern and publish/subscribe, and where do you meet each in frontend code?

    With Observer, the subject keeps its own list of observers and calls them, so each observer subscribes to a specific object.

    import { useSyncExternalStore } from 'react'; // Observer: the subject keeps its own listeners and calls them itselffunction createCounterStore() {  let count = 0;  const listeners = new Set<() => void>();…
  15. 15

    Find the bug: destroy() calls off, 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();  }}

    bind creates a new function on every call, so off receives a function that was never registered and removes nothing.

  16. 16

    What is the Mediator pattern, and how is it different from Observer?

    A mediator coordinates a group of objects that would otherwise call each other: each one reports its changes to the mediator, and the mediator decides who else must change.

    type FieldName = 'country' | 'delivery' | 'isCompany' | 'vatId'; interface FieldState {  value: string;  disabled: boolean;  options?: string[];…
  17. 17

    What are the Strategy and Command patterns, and why are both often just functions or plain objects in TypeScript?

    Strategy makes an algorithm interchangeable: the caller passes in how to price, sort or validate.

    // Strategy: how shipping is priced, picked by keytype ShippingStrategy = (weightKg: number) => number; const shipping = {  standard: (kg) => 500 + kg * 100,  express: (kg) => 1500 + kg * 250,…
  18. 18

    Which classic design patterns does JavaScript make trivial with first-class functions, closures and modules, and which ones still need design?

    Many patterns shrink to a language feature: Strategy and Command become functions, Singleton and Module an ES module, Iterator the iteration protocol.

  19. 19

    How do you apply dependency inversion to a frontend's API layer (ports and adapters), and when is it not worth it?

    Let the business code own the interface it needs (a port such as OrdersGateway, in its own types), and write the HTTP, GraphQL or in-memory code as adapters that implement it.

    // orders/model.ts: the port, in the domain's own termsexport interface Order {  id: string;  totalCents: number;  placedAt: Date;}…
  20. 20

    Which anti-patterns do you meet most in frontend codebases (a god store, a utils dumping ground, the wrong abstraction), and how do you refactor out of them?

    The usual three: a god object every feature reads and writes, a utils folder with no single purpose, and a shared component bent by flags to fit cases it was not made for.