Let's be friends

Introduction

There are so many different “patterns” you might use—reusable ways to structure or compose things in your code. But we don’t want you to feel like you need to memorize them. Patterns are concepts you’d probably stumble upon naturally with enough experimentation and problem-solving.

Instead of overwhelming you, we’re going to organize these patterns based on their practical need—when you’re most likely to use them. But remember, there are lots of ways to group these, and we’ll also note whether they are Creational, Structural, or Behavioral to give you some extra context. (We decided to break these up into many pages)

These patterns are the building blocks of clean code. They’re practical, easy to pick up, and solve problems you’ve likely already run into—like organizing functions or managing shared state. Start here to get comfortable with the essentials.

Target audience: Beginners or developers looking to solidify their understanding of essential patterns.

In order of practical use

  1. JavaScript

    Dynamic key lookup pattern

    // some templates you might use
    var home = "<h1>homepage</h1>";
    var about = "<h1>about page</h1>";
    
    // maybe you get a string from the URL like
    // site.com?page=home
    // but how can you use 'home' to get the variable home ?
    
    // Object to map keys to values
    var pages = {
    	home: home,
    	about: "<h1>about page</h1>",
    }; // you can use an object and [] notation, right?
    
    // dynamic key lookup
    var currentPage = "home";
    
    document.body.innerHTML = pages[currentPage];

    OK. This isn’t really an official pattern or anything, but we think it’s a nice intro to the idea. You’ve probably found yourself in this situation when building your first vanilla JS app.

    You can’t just learn every “pattern” ever. These are just creative ways to use the language to get what you want. Some are “slick” and shorter—but it doesn’t really matter how you get the job done. The goal is to understand the problem and pick a solution that works for you.

    This dynamic key lookup is a practical, lightweight way to access values (like templates or data) based on variable input. It introduces the idea of mapping keys to actions or values, which you’ll encounter in more formal patterns like the Factory or Router.

  2. JavaScript

    Factory pattern

    // example standalone functions of some sort
    function goToBed() {
    	console.log("The monster snuggles into bed and dreams of snacks!");
    }
    
    function chaseTheCat() { ... }
    
    function chowDown() { ... }
    
    
    // "factory" to dynamically select a function
    function MonsterActionFactory(actionName) {
    	const actions = {
    		"go-to-bed": goToBed,
    		"chaseTheCat": chaseTheCat,
    		chowDown,
    	}; // ^ rememember this is short-hand (you choose how to compose these)
    
    	if (!actions[actionName]) {
    		throw new Error(`No action found with name: ${actionName}`);
    	}
    
    	return actions[actionName];
    }
    
    // Example Usage
    const bedtime = MonsterActionFactory('go-to-bed');
    bedtime(); // The monster snuggles into bed and dreams of snacks!
    
    // or
    
    MonsterActionFactory('chowDown')(); // would just run itself

    How do you handle a situation where you have functions with names, but you want to access them programmatically instead of writing if/else all over the place?

    You might have multiple standalone functions, like goToBed, chaseTheCat, and chowDown. You can’t write "chowDown"(). You have some choices to make about how you want this actions object to work:

    • Using kebab-case (“go-to-bed”) might suit you if your inputs are coming from user-friendly strings, like API routes or UI triggers.
    • Using the same case as the function name (e.g., goToBed) can make it more concise and reduce duplication (less chance of typos)

    You can write a function that delegates to a corresponding function based on its name. This is like a little router, right? The actions object serves as a router, mapping string keys to their corresponding functions. The string input (actionName) dynamically determines which function to return from the list of options. The returned function can be:

    • Stored and executed later, or
    • Called immediately.
  3. JavaScript

    Singleton pattern

    const MonsterRegistry = ( function() {
      // private variable to hold the single instance
      let instance;
    
      // Function to create the instance
      function createInstance() {
        return {
          monsters: [],
          addMonster(name) {
            this.monsters.push(name);
            console.log(`${name} has been added to the registry.`);
          },
          getMonsters() {
            return this.monsters;
          },
        };
      }
    
      // Public method to get the single instance
      return {
        getInstance() {
          if (!instance) {
            instance = createInstance();
          }
          return instance;
        },
      };
    })();
    // note IIFE
    
    // example usage
    const registry1 = MonsterRegistry.getInstance();
    registry1.addMonster("Chunky"); // Output: "Chunky has been added to the registry."
    
    const registry2 = MonsterRegistry.getInstance();
    registry2.addMonster("Munchy"); // Output: "Munchy has been added to the registry."
    
    console.log(registry1.getMonsters()); // Output: ["Chunky", "Munchy"]
    console.log(registry1 === registry2); // Output: true (both are the same instance)

    How do you handle a situation where you need a single, shared object in your app—like a central registry or a configuration manager? You might have multiple parts of your code interacting with it, but you only want one instance to exist. That’s where the Singleton Pattern comes in.

    The Singleton Pattern ensures that no matter how many times you try to create the object, there will only ever be one instance. This is useful for things like maintaining a single database connection, tracking application-wide settings, or, in this case, managing a Monster Registry.

    In our example, we’ve created a MonsterRegistry that keeps track of all the monsters you add. By using a private variable (instance) inside an IIFE (Immediately Invoked Function Expression), we ensure that only one registry is ever created. The getInstance method checks if the registry exists—if it does, it returns the same one; if not, it creates it.

    This pattern keeps your code clean and consistent while ensuring shared resources aren’t accidentally duplicated. It’s especially handy for beginners learning how to manage shared state across their apps.

    While the Singleton is a common pattern, overusing it can make your code harder to test and maintain. Use it thoughtfully for things that really need a single, shared instance.

    Type: Creational

  4. JavaScript

    Module pattern

    // module to manage monster inventory
    const MonsterInventory = ( function() {
      // 'private' variables and methods (often denoted with an _underscore)
      let inventory = [];
    
      function logInventory() {
        console.log("Current inventory:", inventory.join(", "));
      }
    
      // Public API
      return {
        addMonster(name) {
          inventory.push(name);
          console.log(`${name} has been added to the inventory.`);
          logInventory();
        },
        removeMonster(name) {
          const index = inventory.indexOf(name);
          if (index > -1) {
            inventory.splice(index, 1);
            console.log(`${name} has been removed from the inventory.`);
          } else {
            console.log(`${name} is not in the inventory.`);
          }
          logInventory();
        },
        getInventory() {
          return [...inventory]; // Return a copy to prevent direct modification
        },
      };
    })();
    
    MonsterInventory.addMonster("Chunky"); // Output: Chunky has been added to the inventory.
    MonsterInventory.addMonster("Munchy"); // Output: Munchy has been added to the inventory.
    MonsterInventory.removeMonster("Chunky"); // Output: Chunky has been removed from the inventory.
    console.log(MonsterInventory.getInventory()); // Output: ["Munchy"]

    The Module Pattern helps you organize your code into reusable, self-contained chunks. It’s especially useful for managing state or functionality without polluting the global scope. Think of it as creating mini-applications within your app, each responsible for specific tasks.

    In this example, MonsterInventory keeps track of the monsters you’re managing. Using an IIFE, it encapsulates the inventory array as a private variable, making it inaccessible from the outside. Instead, we expose a public API with methods like addMonster, removeMonster, and getInventory to interact with the inventory.

    This pattern is classified as Structural because it defines how code is organized and interacts internally. It’s one of the most commonly used patterns in JavaScript, particularly before ES6 modules became widely available.

    We’re showing the module pattern here for understanding, but with modern ESM, you’ll likely achieve the same outcome without writing this by hand—this is essentially what’s happening behind the scenes in Node.js. It’s also a good example of real-world closure (for private state).

  5. JavaScript

    Observer pattern

    // Monster Observer for health monitoring
    const MonsterHealth = (function () {
      let observers = []; // Private list of observers (functions)
    
      function notifyObservers(monster) {
        observers.forEach((observer) => observer(monster));
      }
    
      // Public API
      return {
        subscribe(observer) {
          observers.push(observer);
          console.log("A new observer has subscribed.");
        },
        unsubscribe(observer) {
          observers = observers.filter((obs) => obs !== observer);
          console.log("An observer has unsubscribed.");
        },
        updateHealth(monster, health) {
          monster.health = health;
          console.log(`${monster.name} now has ${health} health.`);
          notifyObservers(monster);
        },
      };
    })();
    
    // example Usage
    const chunky = { name: "Chunky", health: 100 };
    const munchy = { name: "Munchy", health: 80 };
    
    // Define observers
    function alertLowHealth(monster) {
      if (monster.health < 50) {
        console.log(`Warning! ${monster.name} has low health: ${monster.health}`);
      }
    }
    
    function logHealth(monster) {
      console.log(`Logging health: ${monster.name} is now at ${monster.health}.`);
    }
    
    // Subscribe observers
    MonsterHealth.subscribe(alertLowHealth);
    MonsterHealth.subscribe(logHealth);
    
    // Update health
    MonsterHealth.updateHealth(chunky, 40); 
    // Logs: Warning! Chunky has low health: 40
    // Logs: Logging health: Chunky is now at 40
    
    MonsterHealth.updateHealth(munchy, 70); 
    // Logs: Logging health: Munchy is now at 70

    How do you handle a situation where one part of your app needs to react automatically when something changes in another part? For example, when a monster’s health changes, you might want to alert the user or log the health update.

    The Observer Pattern solves this problem by allowing objects (observers) to subscribe to an event or state. When the event occurs or the state changes, all subscribed observers are notified automatically. In this case, the MonsterHealth module tracks observers and notifies them whenever a monster’s health is updated.

    This pattern is classified as Behavioral because it focuses on managing interactions and communication between objects. It’s commonly used in reactive programming, event-driven systems, and frameworks like Vue or React.

Let's be friends