Episode 9 — System Design / 9.3 — Creational Design Patterns

9.3 Exercise Questions -- Creational Design Patterns


Section A: Singleton Pattern (Questions 1-9)

Q1. What is the core intent of the Singleton pattern? Name three real-world resources that should typically be singletons.

Q2. Write a basic Singleton class in JavaScript with a getInstance() static method. Show that two calls to getInstance() return the exact same reference.

Q3. Explain why the module-based singleton (exporting an instance rather than a class) is considered idiomatic in Node.js. What feature of the require() system makes this work?

Q4. Given the following code, identify the bug and fix it:

class Logger {
  static instance = null;

  static getInstance() {
    if (!this.instance) {
      this.instance = new Logger();
    }
    return this.instance;
  }
}

class AppLogger extends Logger {}

const a = Logger.getInstance();
const b = AppLogger.getInstance();
console.log(a === b); // What prints? Why? How do you fix it?

Q5. You have a Singleton database connection. Two async functions call getInstance() at the same time before the connection is established. Write code that ensures only ONE connection is created, even with concurrent async callers.

Q6. A colleague argues: "Singletons are just global variables with extra steps." Write three specific counterarguments explaining how a Singleton is different from a plain global variable.

Q7. Write a test suite (using any test framework or pseudocode) that tests a Singleton Cache class. Show how you handle the "state leaks between tests" problem.

Q8. Your application has a ConfigManager singleton. A new requirement says different microservices need different configs. Refactor the Singleton into a solution that supports multiple named configurations without losing the "single source of truth" concept.

Q9. Explain the Singleton's relationship with the Open/Closed Principle. Does a typical Singleton violate it? If so, how would you redesign it?


Section B: Factory Method Pattern (Questions 10-18)

Q10. In your own words, explain the difference between a factory function and a factory class. When would you prefer each?

Q11. Write a factory function createShape(type, params) that returns Circle, Rectangle, or Triangle instances. Each must have an area() and perimeter() method. Include validation.

Q12. Refactor the following code to use the Factory Method pattern:

function processPayment(type, amount) {
  if (type === 'credit') {
    const cc = new CreditCardProcessor();
    cc.validate();
    cc.charge(amount);
  } else if (type === 'debit') {
    const dc = new DebitCardProcessor();
    dc.validate();
    dc.charge(amount);
  } else if (type === 'crypto') {
    const cr = new CryptoProcessor();
    cr.validate();
    cr.charge(amount);
  }
}

Q13. Implement a NotificationFactory with a registry pattern that allows registering new notification types at runtime (e.g., NotificationFactory.register('telegram', TelegramNotification)). Show how to add a new type without modifying the factory class.

Q14. You're building an API that returns different response formats (JSON, XML, CSV). Design a ResponseFormatterFactory that creates the correct formatter based on the Accept header. Write full code.

Q15. Explain how the Factory Method pattern supports the Open/Closed Principle. Give a concrete before/after example.

Q16. Your factory currently uses a switch statement. It has grown to 15 cases. Refactor it to use a Map-based registry. What are the trade-offs?

Q17. Compare the Factory Method pattern to simply using if/else or switch directly in the calling code. Under what conditions is the factory NOT worth the abstraction?

Q18. Write a LoggerFactory that creates different logger instances based on the environment: ConsoleLogger for development, FileLogger for staging, and CloudLogger for production. Each must implement log(message), error(message), and warn(message).


Section C: Abstract Factory Pattern (Questions 19-26)

Q19. Explain the key difference between Factory Method and Abstract Factory using the "one product vs. family of products" distinction. Give an analogy.

Q20. Design an Abstract Factory for a cross-platform notification system that supports iOS and Android. Each platform needs: PushNotification, InAppNotification, and NotificationSound. Write the full class hierarchy.

Q21. You're building a database abstraction layer. Implement an Abstract Factory with two families: MySQL and PostgreSQL. Each family must provide: Connection, QueryBuilder, and SchemaManager. Write skeletal implementations.

Q22. Given the following client code, write the Abstract Factory and concrete factories that would make it work:

function renderDashboard(factory) {
  const chart = factory.createChart();
  const table = factory.createTable();
  const filter = factory.createFilter();

  chart.render(data);
  table.render(data);
  filter.applyTo(chart, table);
}

Q23. When does Abstract Factory become over-engineering? List three scenarios where a simpler pattern (or no pattern) would be better.

Q24. Your Abstract Factory has 4 product types and 3 families. Calculate the total number of classes needed (factories + products + abstract interfaces). Now imagine adding a 5th product type. How many classes change?

Q25. Write a ThemeFactory that produces a consistent set of components: Button, Card, Input, and Typography. Support "light" and "dark" themes. Each component must have a render() method that returns HTML with appropriate CSS classes.

Q26. Combine Abstract Factory with a Singleton: create a UIFrameworkProvider singleton that holds the current theme factory and can be swapped at runtime. Show how all components in the app would update when the theme changes.


Section D: Builder Pattern (Questions 27-34)

Q27. What is the "telescoping constructor" anti-pattern? Write an example with at least 8 parameters, then refactor it using a Builder.

Q28. Implement a QueryBuilder that supports SELECT, INSERT, UPDATE, and DELETE operations with WHERE, ORDER BY, LIMIT, and JOIN clauses. Show usage for each operation type.

Q29. Write an EmailBuilder with a fluent interface that builds email objects with: to, cc, bcc, subject, htmlBody, textBody, attachments, priority, and replyTo. Include validation in the build() method (e.g., must have at least one recipient and a subject).

Q30. Explain the role of the Director class in the Builder pattern. Write a MealDirector that has predefined recipes: buildVeganMeal(), buildKidsMeal(), and buildFamilyMeal(). Each uses the same MealBuilder with different steps.

Q31. Compare the Builder pattern to using a plain configuration object:

// Option A: Builder
new ServerBuilder().setPort(3000).enableCORS().build();

// Option B: Config object
new Server({ port: 3000, cors: true });

When is each approach better? List at least three criteria.

Q32. Implement an HTMLBuilder that constructs valid HTML documents step by step: doctype, head (title, meta, styles), body (header, main, footer). The build() method should return a complete HTML string.

Q33. Your Builder produces immutable objects (frozen with Object.freeze). A colleague wants to modify a built object. Design a toBuilder() method that converts a built product back into a builder for modification.

Q34. Write a URLBuilder with methods like .protocol(), .host(), .port(), .path(), .queryParam(), .fragment(). The build() method should return a valid URL string. Handle edge cases (encoding, trailing slashes).


Section E: Prototype Pattern (Questions 35-40)

Q35. Explain the difference between shallow copy and deep copy. Write code that demonstrates a bug caused by using shallow copy when deep copy was needed.

Q36. Compare three JavaScript cloning methods: spread operator, JSON.parse(JSON.stringify()), and structuredClone(). List what each can and cannot handle (Date, RegExp, Map, functions, circular references).

Q37. Implement a PrototypeRegistry that stores named prototypes and creates clones on demand. Support registering, cloning with overrides, and listing available prototypes.

Q38. You're building a level editor for a game. The user can place pre-built "stamp" objects (trees, rocks, enemies) on a grid. Design this using the Prototype pattern. Include a Stamp class with clone(), a registry of available stamps, and a placeOnGrid(stamp, x, y) function.

Q39. Your application's configuration object is 500 lines of JSON loaded from an API (takes 2 seconds). Different parts of the app need slightly different versions of this config. Refactor the current approach (which loads from the API each time) to use the Prototype pattern. Show the performance improvement.

Q40. Explain how Object.create() relates to but differs from the Prototype pattern. Write code showing both approaches and explain when you'd use each.


Section F: Cross-Pattern Questions (Questions 41-45)

Q41. You're designing a notification system. Walk through your decision process for choosing between Factory Method, Abstract Factory, and Builder. What questions would you ask about the requirements to decide?

Q42. Can you combine Builder and Prototype? Design a system where a Builder creates a complex template object, and then Prototype clones it for each user. Write the code.

Q43. Refactor the following code to use the most appropriate creational pattern(s). Justify your choice:

function createServer(env) {
  const server = {};
  server.port = env === 'prod' ? 8080 : 3000;
  server.host = env === 'prod' ? '0.0.0.0' : 'localhost';
  server.db = env === 'prod'
    ? new PostgresDB('prod-host', 5432, 50)
    : new SQLiteDB('./dev.db');
  server.cache = env === 'prod'
    ? new RedisCache('redis-host', 6379)
    : new MemoryCache();
  server.logger = env === 'prod'
    ? new CloudLogger('datadog-key')
    : new ConsoleLogger();
  return server;
}

Q44. For each scenario, name the most appropriate creational pattern and explain why in one sentence:

  • a) Ensuring only one WebSocket connection manager exists
  • b) Creating platform-specific UI components (web, mobile, desktop)
  • c) Constructing SQL queries with optional clauses
  • d) Spawning 1000 enemy NPCs with similar stats but different positions
  • e) Creating payment processors based on user selection
  • f) Building an immutable HTTP response object with many optional headers

Q45. Design a mini e-commerce system that uses ALL five creational patterns:

  • Singleton for the shopping cart
  • Factory Method for creating products
  • Abstract Factory for creating payment + shipping families (domestic vs international)
  • Builder for constructing order objects
  • Prototype for creating product variations from templates

Write the class hierarchy and show how they interact. You can use pseudocode for brevity, but the structure must be clear.