30 Days of JavaScript: JavaScript Design Patterns, Day 22

30 Days of JavaScript: Building a Simple Weather App
30 Days of JavaScript: JavaScript Design Patterns, Day 22

Learn essential JavaScript design patterns, including Module, Singleton, and Observer. Discover how these patterns improve code organization, scalability, and efficiency.

You’ve solved the same problem more than once. A global that everything reaches into and nobody owns. A settings object that five files quietly mutate. A screen that forgets to redraw when the data behind it changes. Design patterns are the names people gave those fixes after enough of us hit the same wall.

Day 22 of the 30-day JavaScript run is about three of them: the Module, Singleton, and Observer patterns. None are magic. Each is a shape you reach for when the code starts to sprawl. We’ll build all three in plain JavaScript, and we’ll be honest about where the language has since made some of this easier.

Table of Contents

Understanding Design Patterns

What are Design Patterns?

A design pattern is a repeatable answer to a problem that keeps showing up. It isn’t a library you install or a snippet you paste. It’s a shape, a way of arranging objects and calls that has proven itself across enough projects to earn a name. Patterns aren’t tied to one language either. The three here come out of object-oriented design and translate cleanly into JavaScript.

Why Use Design Patterns?

Two reasons, and neither is “because a book said so.” First, you stop reinventing a fix that already has a known-good shape. Second, the name is shared vocabulary. Say “let’s make that a singleton” in a review and everyone knows what you mean without a paragraph of setup. That’s the real payoff: less arguing about structure, more agreeing on it.

The trap is reaching for a pattern before you have the problem. Patterns earn their place by removing pain you can already feel, not pain you imagine you might.

Categories of Design Patterns
  • Creational: how objects get made, so creation stays flexible instead of hard-coded.
  • Structural: how objects and classes fit together into larger structures.
  • Behavioral: how objects talk to each other and share responsibility.

Singleton is a creational pattern. Observer is behavioral. The Module pattern isn’t in the original Gang of Four catalog at all; it’s a JavaScript idiom for encapsulation, which is why it feels a little different from the other two.

Module Pattern

Introduction to the Module Pattern

For years, the Module pattern was how you got privacy in JavaScript. The language had no file-level scope, so anything you declared risked colliding on the global object. The pattern’s job is simple: wrap your code so the internals stay hidden and only a small public API leaks out.

One thing worth saying up front, because most old tutorials skip it. Since ES2015, JavaScript has real modules. Any file loaded with import and export gets its own private scope for free, and surveys now put ES module usage well above 80% of developers. So you’ll rarely hand-write this pattern in new code. You still need to read it, because it’s everywhere in older projects and in the bundled output of build tools.

Implementing the Module Pattern

The tool is an Immediately Invoked Function Expression, an IIFE. You define a function, run it on the spot, and return an object. That returned object is your public API. Everything else stays trapped in the closure.

Example:

JS
/**
 * Demonstrates the Module Pattern by encapsulating private variables and methods.
 *
 * @module myModule
 */
const myModule = (function () {
  let privateVariable = 'I am private';
  /**
   * Logs the private variable to the console.
   *
   * @private
   */
  function privateMethod() {
    console.log(privateVariable);
  }
  return {
    /**
     * Public method that calls the private method.
     */
    publicMethod: function () {
      privateMethod();
    }
  };
})();
myModule.publicMethod(); // Outputs: I am private

privateVariable and privateMethod live inside the function and never escape it. The outside world only sees publicMethod. That’s encapsulation with nothing more than a closure.

Advantages and Use Cases of the Module Pattern
  • Encapsulation: private data and methods stay out of reach.
  • Organization: related code sits in one named unit.
  • Reusability: drop the module in wherever you need it.

Reach for it on a small library or plugin where you want a clean seam between public and private. For a whole app, prefer real ES modules and keep this pattern for the legacy code you’ll inevitably meet.

Singleton Pattern

Understanding the Singleton Pattern

A Singleton guarantees one instance and one shared way to reach it. Think of the things there should only ever be one of: an app config, a logger, a single cache. Create two of those by accident and you get bugs that are miserable to trace, because now there are two sources of truth.

Here’s the modern footnote most tutorials leave out. An ES module is already a singleton. The spec evaluates each module exactly once and hands every importer the same instance, so export const config = {} gives you a shared single object without any of the machinery below.

And a warning worth saying plainly: a singleton is shared mutable state wearing a nicer name. It’s fine for a stateless logger. It’s a landmine for anything that should be per-request, especially on a long-running server where every request shares the one instance.

Implementing the Singleton Pattern

You can build one with a closure that hides the instance and only creates it on the first request.

Example:

JS
/**
 * Demonstrates the Singleton Pattern by creating a single instance of an object.
 *
 * @module Singleton
 */
const Singleton = (function () {
  let instance;
  /**
   * Creates a new instance of an object.
   *
   * @returns {Object} A single instance object.
   */
  function createInstance() {
    const object = new Object('I am the instance');
    return object;
  }
  return {
    /**
     * Returns the single instance of the object, creating it if it doesn’t exist.
     *
     * @returns {Object} The single instance.
     */
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

getInstance checks whether the instance exists, creates it once if it doesn’t, and returns that same object on every call after. The instance1 === instance2 check proves it: both names point at one object.

Advantages and Use Cases of the Singleton Pattern
  • Resource management: one database connection or pool instead of many.
  • Global access: one known place to reach shared state.
  • Consistency: one instance means one source of truth.

Best fit is when creating the object is expensive, or when the whole app genuinely needs to agree on one piece of state. If you find yourself using it just to dodge passing an argument around, that’s a smell, not a pattern.

Observer Pattern

Introduction to the Observer Pattern

The Observer pattern handles a one-to-many relationship: one object changes, and a crowd of others need to hear about it. The object being watched is the subject. The listeners are observers. The subject keeps a list of them and calls each one when something happens.

It’s worth drawing a line here, because two patterns get mixed up. In the Observer pattern, the subject holds direct references to its observers and notifies them itself. In Publish/Subscribe, a channel or event bus sits in the middle and the two sides never know about each other. The example below is Observer proper: the subject knows exactly who’s listening.

Implementing the Observer Pattern

You need a subject that can add, remove, and notify observers, plus observers that know how to react to an update.

Example:

JS
/**
 * Subject class that manages and notifies observers of state changes.
 */
class Subject {
  constructor() {
    this.observers = [];
  }
  /**
   * Adds an observer to the list.
   *
   * @param {Observer} observer - The observer to add.
   */
  subscribe(observer) {
    this.observers.push(observer);
  }
  /**
   * Removes an observer from the list.
   *
   * @param {Observer} observer - The observer to remove.
   */
  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  /**
   * Notifies all observers of a data change.
   *
   * @param {*} data - The data to notify observers with.
   */
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}
/**
 * Observer class that defines how observers react to data changes.
 */
class Observer {
  /**
   * Handles updates from the subject.
   *
   * @param {*} data - The data provided by the subject.
   */
  update(data) {
    console.log(`Observer received data: ${data}`);
  }
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Notification #1');

Subject owns the list and drives it: subscribe adds a listener, unsubscribe drops one, and notify pushes data to all of them. Each Observer decides what to do in its own update method.

Advantages and Use Cases of the Observer Pattern
  • Event handling: registering and firing listeners and callbacks.
  • MVC architecture: the model tells its views when it changes.
  • Real-time apps: broadcasting to many subscribers at once, like a chat feed or a live dashboard.

Any time one change needs to ripple out to several places without the subject caring what those places do with it, this is the shape. It’s the pattern hiding under a lot of the reactive tooling you already use.

Implementing Design Patterns in Projects

Integrating the Module Pattern in a Project

Here’s the Module pattern doing real work instead of demonstrating itself. This calculator keeps a private history array that callers can’t touch directly. They can add, subtract, and ask for the history, and nothing more. The list stays honest because nothing outside can reach in and rewrite it.

Example:

JS
/**
 * Calculator module with add and subtract functions and a history feature.
 *
 * @module calculatorModule
 */
const calculatorModule = (function () {
  let history = [];
  /**
   * Adds two numbers and stores the operation in history.
   *
   * @param {number} a - The first number.
   * @param {number} b - The second number.
   * @returns {number} The sum of a and b.
   */
  function add(a, b) {
    const result = a + b;
    history.push(`${a} + ${b} = ${result}`);
    return result;
  }
  /**
   * Subtracts the second number from the first and stores the operation in history.
   *
   * @param {number} a - The first number.
   * @param {number} b - The second number.
   * @returns {number} The difference of a and b.
   */
  function subtract(a, b) {
    const result = a - b;
    history.push(`${a} - ${b} = ${result}`);
    return result;
  }
  /**
   * Retrieves the history of operations.
   *
   * @returns {Array<string>} The history of operations.
   */
  function getHistory() {
    return history;
  }
  return { add, subtract, getHistory };
})();
console.log(calculatorModule.add(5, 3)); // 8
console.log(calculatorModule.getHistory()); // ["5 + 3 = 8"]

Conclusion

Three patterns today, with one honest through-line: each is a named answer to a problem you’ll actually hit. The Module pattern draws a line between public and private. The Singleton keeps everyone pointed at one instance. The Observer lets one change notify many listeners.

The other honest thread is that JavaScript has moved. ES modules give you the Module pattern and a working Singleton almost for free, so a lot of the old ceremony is now optional. That doesn’t make the patterns useless. It makes knowing the shape more valuable than memorizing the boilerplate, because you’ll still read this code in older projects and need to recognize what it’s reaching for.

Use them where they remove pain you can feel. Skip them where they’d only add ceremony. Tomorrow, Day 23, we move to GraphQL and JavaScript: setting up Apollo Client, writing queries, and pulling data more cleanly than a pile of REST calls.

What’s Next?

In Day 23, we’ll explore GraphQL and JavaScript. You’ll wire up Apollo Client, run your first queries, and see how a typed graph changes the way you fetch data in a modern application.

Next: 30 Days of JavaScript: GraphQL and JavaScript, Day 23

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top