completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Structural & behavioral patterns

Learn decorator, proxy, adapter, facade, strategy, command, and middleware patterns through runnable JavaScript examples and undo redo flows.

By the end, you can
  • 01
    Wrap code without changing itUse a decorator or a proxy to add logging, a cache, or checks around a function or object that already works.
  • 02
    Clean up outside dataWrite an adapter that turns outside data into your app's shape, and a facade that hides several steps behind one call.
  • 03
    Pick, save, and chain actionsUse a strategy to pick a function by name, a command to undo and redo, and middleware to run a chain of steps.

Names for common code shapes

A design pattern is a name for a shape of code that shows up again and again. Patterns are not rules. You don't have to use them in every file. They give a team short words for common jobs, such as:

  • "This function wraps another one and adds a log line."
  • "This function turns another company's data into our shape."
  • "This action can be undone."
The main idea

Patterns come in families. This lesson covers two of them:

  • Structural patterns are about how pieces of code fit together: which one wraps, guards, or talks to another. You'll meet decorator, proxy, adapter, and facade.
  • Behavioral patterns are about how work gets done: which function is picked, how an action is saved, and how work passes along a chain. You'll meet strategy, command, and middleware.
Real-life analogyTravel adapters and a hotel concierge

On a trip, a plug adapter lets your charger fit a socket of a different shape. At the hotel, you ask the concierge, the front-desk helper, to plan dinner. The concierge calls the restaurant and the taxi for you. At an office door, a security desk checks who may enter.

In real life: A plug adapter lets your plug fit a foreign socket
In JavaScript: An adapter turns outside data into the shape your app uses
In real life: A concierge takes one request and asks several staff to do the work
In JavaScript: A facade is one simple call that makes several calls for you
In real life: A security desk decides who may go in
In JavaScript: A proxy decides how code can read or change an object

The table below compares the four structural patterns. Read each row as: the pattern, what it does, and one small example from this lesson.

Four structural patterns side by side
PatternWhat it doesSmall example
DecoratorIt wraps a function or object and adds work before or after the original call.withRetry(fetchJson) still calls fetchJson. It only adds a second try when the first one fails.
ProxyIt stands in front of an object and decides what happens when code reads or changes it.new Proxy(settings, traps) can check each new value before it is saved.
AdapterIt turns data or a function from outside into the shape your app uses.fromWeatherApiB(raw) returns the same { city, celsius } shape as service A.
FacadeIt gives one simple call that runs several steps for you, in the right order.media.play("intro") loads the video, sets the volume, records the play, and starts it.

Some nearby patterns have their own lessons:

Decorator and proxy: wrap or stand in

WRAPPERS

A decorator wraps a function or an object and adds a little extra work around it. The original code doesn't change. A function that wraps another function like this is a higher-order function: it takes a function and gives back a new one. Some common decorators:

  • withLogging(fn) prints a line each time fn is called.
  • withRetry(fn) calls fn again when it fails.
  • memoize(fn) remembers earlier answers, so fn doesn't run twice for the same input.

The memoization lesson goes deeper on remembering answers. The decorators lesson covers the @decorator syntax, a newer JavaScript feature for classes. This section is about the pattern, which works with plain functions.

A small decorator adds taxPop out in the code editor (opens in a new tab)JavaScript
function withTax(fn) {
  return (price) => Math.round(fn(price) * 1.1);
}
const priceWithTax = withTax((price) => price);
console.log(priceWithTax(10));

Line 1 takes a function, fn. Line 2 gives back a new function. The new function calls fn, multiplies the result by 1.1 to add 10% tax, and rounds it with Math.round. The rounding is needed because 10 * 1.1 is really 11.000000000000002 in JavaScript. Line 4 wraps a function that just returns the price it gets. Line 5 prints 11.

One warning: if you wrap a method that uses this, the wrapper must pass this along. Otherwise the method loses its object.

Two decorators: a log and a cachePop out in the code editor (opens in a new tab)JavaScript
function withLogging(fn) {  return function logged(...args) {    console.log("calling " + fn.name + "(" + args.join(", ") + ")");    const result = fn(...args);    console.log("returned " + result);    return result;  };} function withMemo(fn) {  const cache = new Map();  return function memoized(value) {    if (cache.has(value)) return cache.get(value);    const result = fn(value);    cache.set(value, result);    return result;  };} function priceWithTax(price) {  return Number((price * 1.1).toFixed(2));} const loggedPrice = withLogging(withMemo(priceWithTax));console.log(loggedPrice(20));console.log(loggedPrice(20));

Read it in parts:

  • Lines 1–8: withLogging gives back a new function, logged. It prints the call, calls the original on line 4, then prints the result.
  • Lines 10–18: withMemo makes a cache on line 11. A cache is a store of earlier answers; here it is a Map from each input to its answer. If the input is already there, line 13 returns the saved answer. If not, line 14 calls the original and line 15 saves the answer. The cache stays alive between calls because memoized is a closure: a function that keeps the variables around it.
  • Lines 20–22: priceWithTax is the original function. It adds 10% tax and rounds to two decimal places.
  • Line 24 wraps it twice. A call goes through logged first, then memoized, then priceWithTax.
  • Lines 25 and 26 call it twice with 20. Each call prints calling memoized(20), returned 22, and 22. The log says memoized because that is the name of the function withLogging wraps.
The input 20 goes into the logged function, which prints the call. Then into memoized, which checks the cache. Then into priceWithTax, which returns 22.withLogging(withMemo(priceWithTax))20inputloggedprints the callmemoizedchecks the cachepriceWithTaxreturns 22
loggedPrice(20) passes through both wrappers to priceWithTax, which returns 22. On the second call, the cache already has 20, so priceWithTax doesn't run again.

You can wrap an object the same way. The wrapper object has the same methods. Each method calls the original object's method and adds some extra work, such as timing or a permission check.

A proxy is different. It is a stand-in: an object that sits in front of another object, called the target. Code talks to the proxy, and the proxy decides what happens with each read and write. You can write a proxy as a plain object, or use JavaScript's built-in Proxy together with Reflect.

A proxy gives a default valuePop out in the code editor (opens in a new tab)JavaScript
const user = { name: "Asha" };
const safeUser = new Proxy(user, {
  get(target, key) { return key in target ? target[key] : "unknown"; },
});
console.log(safeUser.name, safeUser.role);

Line 1 makes the target, user. Line 2 puts a Proxy in front of it. Line 3 is a trap: a function the proxy runs when code reads a property. If the target has the property, the trap returns it. If not, it returns "unknown". So line 5 prints Asha unknown, and user itself doesn't change.

Each trap is one more step between your code and the object, so errors are harder to trace. If you don't need to catch reads and writes, a plain function is simpler.

A plain stand-in, then a built-in ProxyPop out in the code editor (opens in a new tab)JavaScript
const stock = { notebook: 3 }; const guardedStock = {  get(name) {    return name in stock ? stock[name] : 0;  },  set(name, value) {    if (value < 0) throw new Error("Stock cannot be negative");    stock[name] = value;  },}; guardedStock.set("pen", 2);console.log(guardedStock.get("pen"));console.log(guardedStock.get("missing")); const settings = new Proxy({ theme: "dark" }, {  get(target, key, receiver) {    console.log("read " + String(key));    return Reflect.get(target, key, receiver);  },  set(target, key, value, receiver) {    if (key === "theme" && !["dark", "light"].includes(value)) {      throw new Error("Unsupported theme");    }    return Reflect.set(target, key, value, receiver);  },}); console.log(settings.theme);settings.theme = "light";console.log(settings.theme);

Read it in three parts:

  • Lines 3–11 are a plain stand-in object. Its get returns 0 for an item that isn't in stock. Its set throws an error for a number below 0. Line 14 prints 2, and line 15 prints 0 for "missing", because line 13 added 2 pens and nothing called "missing".
  • Lines 17–28 use the built-in Proxy. The get trap prints read theme on every read (line 19). The set trap on line 22 throws for any theme other than "dark" or "light". Line 26 calls Reflect.set, which does the normal write. Reflect has one function for each normal object action, so a trap can do its check and then hand the action back.
  • Line 30 prints read theme and dark. Line 31 changes the theme. Line 32 prints read theme and light.

Proxies are good for checking new values, logging, loading data only when it is first read, and controlling who can change what. If a plain function call does the job, use that. It is easier to debug.

Adapter and facade: translate or simplify

BOUNDARIES

An API is the set of functions and data shapes that one piece of code offers to others. Data from outside your app often comes in a shape you don't want. It might come from a weather service, a payment company, or an old library. An adapter is a small function that turns that outside shape into your app's own shape.

The adapter sits at the boundary: the place where outside data enters your app. Then the rest of your app never sees the outside shape. If the outside shape changes, only the adapter changes.

A small adapter renames a fieldPop out in the code editor (opens in a new tab)JavaScript
function adaptUser(user) {
  return { name: user.full_name, admin: user.role === "admin" };
}
console.log(adaptUser({ full_name: "Asha", role: "admin" }));

The outside data calls the name full_name. Your app wants name. Line 2 copies the name across. It also turns the text "admin" into the true-or-false value admin: true. Line 4 prints { name: 'Asha', admin: true }. This happens once, at the boundary, not in every part of the app that shows a profile.

A cart called outside holds full_name Asha. An arrow goes to the adaptUser function. An arrow goes on to a cart called app, which holds name Asha.full_name: "Asha"outsideadaptUser()renames the fieldname: "Asha"app
adaptUser() takes the outside shape, with full_name, and returns your app's shape, with name. It also turns role: "admin" into admin: true.
Adapters for two weather services and an old callback functionPop out in the code editor (opens in a new tab)JavaScript
function fromWeatherApiA(response) {  return { city: response.city, celsius: response.tempC };} function fromWeatherApiB(response) {  return {    city: response.location.name,    celsius: Math.round((response.current.tempF - 32) * 5 / 9),  };} function readLegacyProfile(id, callback) {  callback(null, { id, name: "Ada" });} function profilePromise(id) {  return new Promise((resolve, reject) => {    readLegacyProfile(id, (error, profile) => {      if (error) reject(error);      else resolve(profile);    });  });} const reports = [  fromWeatherApiA({ city: "Chennai", tempC: 31 }),  fromWeatherApiB({ location: { name: "London" }, current: { tempF: 59 } }),]; console.log(reports.map((report) => report.city + ":" + report.celsius).join(" | "));profilePromise(7).then((profile) => console.log(profile.name));

Read it in parts:

  • Lines 1–3 and 5–10 read two different weather shapes. Service A gives tempC, in Celsius. Service B gives tempF, in Fahrenheit, inside other objects. Both adapters return the same shape: { city, celsius }.
  • Lines 25–28 run both adapters. Line 30 prints Chennai:31 | London:15, because 59°F is 15°C.
  • Lines 12–14 are an old function that takes a callback: a function it calls when it is done. Lines 16–23 adapt it so it returns a promise instead: an object that will hold the result once it arrives. Line 31 prints Ada.

Now callers can use .then() or await instead of a callback. The promisification and async/await lessons go deeper.

A facade is one simple function in front of a subsystem: a group of parts that must be called in several steps. The rest of your app calls the facade. It doesn't need to know the steps or their order.

A small tea facadePop out in the code editor (opens in a new tab)JavaScript
const kitchen = {
  boil() { return "boiled"; },
  pour() { return "tea"; },
};
const makeTea = () => { kitchen.boil(); return kitchen.pour(); };
console.log(makeTea());

Lines 1–4 are the subsystem: a kitchen with two steps. Line 5 is the facade. makeTea() boils, then pours, and returns what pour() gives back. Line 6 prints tea. The caller only knows makeTea().

A facade isn't worth it for a single step that only one place uses. It helps when several parts of the app need the same steps in the same order.

A media.play() facade over four stepsPop out in the code editor (opens in a new tab)JavaScript
function createMediaFacade(video, analytics) {  return {    play(title) {      video.load(title);      video.setVolume(0.7);      analytics.track("play:" + title);      return video.start();    },  };} const video = {  load(title) { console.log("load " + title); },  setVolume(level) { console.log("volume " + level); },  start() { return "playing"; },};const analytics = { track(event) { console.log("track " + event); } }; const media = createMediaFacade(video, analytics);console.log(media.play("intro"));

createMediaFacade gets a video player and an analytics tool: code that counts what users do. Its play method on line 3 does four steps:

  1. Line 4 loads the video.
  2. Line 5 sets the volume.
  3. Line 6 records the play.
  4. Line 7 starts the video and returns "playing".

Line 19 builds the facade. Line 20 is the one call the rest of the app makes. It prints load intro, volume 0.7, track play:intro, and then playing.

The steps inside can change later without changing any caller. A fetch helper is often a facade too: one call that adds login headers, reads the JSON, and turns failures into clear error messages.

Strategy: pick a function by its name

INTERACTIVE

A strategy is one of several functions that do the same job in different ways. They all take the same input and give back the same kind of result. You keep them in one object, each under a name, and pick one by its name while the program runs.

This can replace a long switch statement or a long if/else chain. Shipping prices, discounts, sorting, and formatting are common examples.

Two formatters, picked by namePop out in the code editor (opens in a new tab)JavaScript
const formatters = {
  upper: (name) => name.toUpperCase(),
  lower: (name) => name.toLowerCase(),
};
console.log(formatters.upper("Asha"));

Both functions take a name and return text. Line 5 picks upper and prints ASHA. If there are only two or three choices that never change, a plain if is often clearer.

Change the shipping strategy
Three shipping functions, picked by namePop out in the code editor (opens in a new tab)JavaScript
const shippingStrategies = {  standard(cart) {    return cart.subtotal >= 75 ? 0 : 7;  },  express(cart) {    return 12 + cart.items * 2;  },  pickup() {    return 0;  },}; function quoteShipping(cart, strategyName) {  return shippingStrategies[strategyName](cart);} const cart = { subtotal: 80, items: 2 };console.log(quoteShipping(cart, "standard"));console.log(quoteShipping(cart, "express"));console.log(quoteShipping(cart, "pickup"));
Shippingstandard
cart total$80
items2
shipping fee$0
Try it yourself

You picked standard. Line 14 looks up shippingStrategies.standard and calls it with the cart. The fee is $0.

The cart stays the same. Only the name of the shipping function changes.

Try each button. Lines 1–11 hold the three shipping functions. Line 14 picks one by its name, with shippingStrategies[strategyName], and calls it with the cart. For this cart:

  • standard is 0, because the total, 80, is at least 75.
  • express is 16: 12, plus 2 for each of the 2 items.
  • pickup is always 0.

The caller only passes a cart and a name. To add overnight shipping, you add one more function to the object. You don't edit quoteShipping. A strategy is worth it when the choices grow, come from settings, or need their own tests.

Command: actions you can undo and redo

STEP THROUGH

A command is an object that stands for one action. It has two methods:

  • execute() does the action.
  • undo() reverses it.

A history manager keeps commands in two stacks. A stack is a list where you add to the end and take from the end, like a pile of plates. The manager doesn't need to know what each command does. It only calls execute() and undo(). Text editors, drawing apps, task queues, and activity logs use this shape.

A small action that can be undonePop out in the code editor (opens in a new tab)JavaScript
const todo = { done: false };
const complete = {
  execute() { todo.done = true; },
  undo() { todo.done = false; },
};
complete.execute();
console.log(todo.done);

Line 1 is a to-do item that is not done yet. Lines 2–5 are the command. Line 6 runs it, so line 7 prints true. Calling complete.undo() would set it back to false.

Each command must know exactly how to reverse its own work. The editor below adds text at the end, so its undo removes the same number of characters from the end. That only works because nothing else changes the text in between. Real editors save a copy of the old text, or a full reverse step, for bigger edits.

Command history: run, undo, redo, then a new edit
Step 0 of 21Ready
Your turn: follow the blue line

A command is an object with execute() and undo(). Step through a small editor history and watch undo, redo, and the rule that a new command empties the redo list.

Running in
  1. script
Next: line 1
  let text = initialText;  return {    getText: () => text,    insert: (value) => { text += value; },    removeEnd: (count) => { text = text.slice(0, -count); },  };} function insertText(editor, value) {  return {    execute() { editor.insert(value); },    undo() { editor.removeEnd(value.length); },    label: "insert " + JSON.stringify(value),  };} function createCommandManager() {  const undoStack = [];  const redoStack = [];  return {    run(command) {      command.execute();      undoStack.push(command);      redoStack.length = 0;    },    undo() {      const command = undoStack.pop();      if (!command) return;      command.undo();      redoStack.push(command);    },    redo() {      const command = redoStack.pop();      if (!command) return;      command.execute();      undoStack.push(command);    },    counts: () => ({ undo: undoStack.length, redo: redoStack.length }),  };} const editor = createEditor("Hi");const history = createCommandManager();history.run(insertText(editor, "!"));console.log(editor.getText(), JSON.stringify(history.counts()));history.undo();console.log(editor.getText(), JSON.stringify(history.counts()));history.redo();console.log(editor.getText(), JSON.stringify(history.counts()));history.undo();history.run(insertText(editor, "?"));console.log(editor.getText(), JSON.stringify(history.counts()));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Step through the whole history:

  1. Line 45 runs a command that adds !. The text is Hi!.
  2. Line 47 undoes it. The command moves from undoStack to redoStack.
  3. Line 49 redoes it. The command moves back.
  4. Line 51 undoes it again.
  5. Line 52 runs a new command that adds ?. The text is Hi?.
Two boxes, undoStack and redoStack. After history.undo(), undoStack is empty and redoStack holds the command insert !. An arrow labelled undo() goes from undoStack to redoStack. An arrow labelled redo() goes back.history.undo()undoStackemptyundo()redo()insert "!"redoStack1 command
undo() moves a command from undoStack to redoStack, and redo() moves it back. After line 47, the insert "!" command waits in redoStack.

The main rule is on line 25: a new command empties the redo stack. Once the user makes a new edit, the old ! belongs to a path they left, so it can't be redone.

Middleware: a chain that calls next()

IN AND OUT

Middleware is a chain of functions that each do part of the work on one request. Each function gets two things:

  • a context object, shared by the whole chain, that holds the request's data;
  • next(), a function that runs the rest of the chain.

Code before next() runs on the way in. Code after next() runs on the way out, once the rest of the chain is done. Express and Koa, two Node.js server libraries, use this idea. So does Redux, a library for app data, for its own middleware.

A small chain with one wrapperPop out in the code editor (opens in a new tab)JavaScript
const log = [];
const withLabel = (next) => { log.push("before"); next(); log.push("after"); };
withLabel(() => log.push("work"));
console.log(log.join(" > "));

Line 2 is a wrapper. It pushes before, calls next(), then pushes after. Line 3 calls it and passes the main work as next. Line 4 prints before > work > after.

Real middleware also needs a plan for errors. It should also stop a function from calling next() twice, because that would run the rest of the chain twice.

A middleware runner without await
Run each middleware in orderPop out in the code editor (opens in a new tab)JavaScript
function compose(middleware) {  return (context) => {    let index = -1;    function dispatch(i) {      if (i <= index) throw new Error("next() called twice");      index = i;      const fn = middleware[i];      if (!fn) return;      return fn(context, () => dispatch(i + 1));    }    dispatch(0);    return context.log;  };} const stack = [  (ctx, next) => { ctx.log.push("auth before"); next(); ctx.log.push("auth after"); },  (ctx, next) => { ctx.log.push("timing before"); next(); ctx.log.push("timing after"); },  (ctx) => { ctx.log.push("handler"); },]; const app = compose(stack);console.log(app({ log: [] }).join(" -> "));
Outputin, then out

auth before -> timing before -> handler -> timing after -> auth after

Try it yourself
Output of the code on the left

Each middleware calls next() to run the next one. Lines before next() run on the way in. Lines after next() run on the way out, as each call returns.

Express, a Node.js server library, uses this same shape for its middleware.

The runner is a function called compose. It keeps a counter, index, for the last middleware it ran. If a function calls next() twice, line 5 throws an error. Line 9 calls the current middleware and hands it a next that runs the one after it.

The async version below changes one thing: next() returns a promise, and each middleware waits for it with await.

Middleware that uses await next()
Step 0 of 13Ready
Your turn: follow the blue line

Middleware is a chain of functions. Step through the async version and watch the order that await next() creates.

Running in
  1. script
Next: line 1
  return async (context) => {    let index = -1;    async function dispatch(i) {      if (i <= index) throw new Error("next() called twice");      index = i;      const fn = middleware[i];      if (!fn) return;      await fn(context, () => dispatch(i + 1));    }    await dispatch(0);    return context.log;  };} const stack = [  async (ctx, next) => {    ctx.log.push("auth before");    await next();    ctx.log.push("auth after");  },  async (ctx, next) => {    ctx.log.push("timing before");    await next();    ctx.log.push("timing after");  },  async (ctx) => {    ctx.log.push("handler");  },]; async function main() {  const app = composeAsync(stack);  const log = await app({ log: [] });  console.log(log.join(" -> "));}main();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Because of await next(), the order is like the layers of an onion. Auth starts on the outside. Timing starts inside it. The handler is the middle. Then timing finishes, and auth finishes last.

Five log entries in a V shape. Going down: auth before, timing before, then handler at the bottom. Going up: timing after, then auth after.auth beforeway intiming beforehandlertiming afterway outauth after
The before lines run on the way in. After the handler, each await next() finishes, so the after lines run on the way out, in reverse order.

Choosing a pattern without extra code

Patterns often mix. A decorator may use a closure. A proxy may keep a cache. A facade may call adapters. That is normal. Pick the name that best explains the job of the code you are writing.

The table below says when each pattern is worth it. Read each row as: the pattern, how often it pays off, and the job that calls for it.

When each pattern is worth the extra code
PatternIs it worth it?Use it when
DecoratorOften.Many functions need the same extra work, such as logging, a second try, a cache, timing, or a permission check.
ProxySometimes. It is powerful but harder to debug.Every read or write of an object must be checked, logged, or delayed, and callers should not touch the object directly.
Adapter or facadeVery often.Your code talks to outside services, libraries, browser features, or old functions that take callbacks.
StrategyWhen the choices keep changing.You pick one of several ways to do a job while the program runs, such as shipping prices, discounts, sorting, or formatting.
CommandWhen you need a history.Actions must be undone, redone, saved in a queue, tried again, or written to a log.
MiddlewareWhen work passes through steps.A request or action passes through login checks, logging, and timing before the main handler runs.
Six calls from this lessonPop out in the code editor (opens in a new tab)JavaScript
const examples = [  "withRetry(fetchJson)",  "new Proxy(settings, traps)",  "fromWeatherApiB(response)",  "shippingStrategies.express(cart)",  "history.run(insertText(editor, '!'))",  "await logger(ctx, next)",]; console.log(examples.length);

Each text in this list is a call you met in this lesson. Line 10 prints 6, the number of calls. Now sort the same calls by the job they do.

Which pattern family is this?
  • const reliableFetch = withRetry(fetchJson);
  • new Proxy(settings, { set(target, key, value) { ... } })
  • fromWeatherApiB(rawResponse)
  • media.play("intro")
  • shippingStrategies[selected](cart)
  • history.run(insertText(editor, "!"))
Try it yourself
0 of 6 correct

Sort each card by the job its code does.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Three more patterns are worth knowing by name:

  • Composite treats one item and a group of items the same way, like a file and a folder.
  • Iterator is a standard way to walk through a collection, one item at a time.
  • Chain of responsibility passes a request along a line of handlers until one of them handles it. Middleware is a kind of chain where every step shares one context and calls next().

Common mix-ups

  • "Decorator means the @decorator syntax." The syntax is one language feature. The pattern is just wrapping, and plain functions can do it.
  • "Proxy always means the built-in Proxy." A plain stand-in object is also a proxy when it guards how code reads or changes another object.
  • "Adapter and facade mean the same thing." An adapter changes a shape. A facade hides several steps behind one call.
  • "A strategy is always better than switch." A short switch is often clearer, until the choices grow or come from settings.
  • "Middleware is only for servers." Redux, logging in the browser, feature switches, and analytics can all use the same chain idea.

The table below pairs up patterns that look alike. Read each row as: the two names, one question that tells them apart, and which one to pick.

Patterns that look alike but do different jobs
Mix-upQuestion to askWhich one to pick
Decorator or proxyAm I adding work around a call, or guarding how code reads and changes an object?Pick a decorator to add work. Pick a proxy to guard an object.
Adapter or facadeAm I changing a shape, or hiding several steps behind one call?Pick an adapter to change a shape. Pick a facade to hide steps.
Strategy or commandAm I picking a function to run now, or saving an action to undo later?Pick a strategy to choose. Pick a command to keep a history.
Middleware or chain of responsibilityDoes every step share one context and call next(), or does one handler take the request and stop?Pick middleware when every step runs. Pick a chain when one handler takes over.

Practice exercises

Exercise 1 · Warm-upRead a decorator

What does the last line print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function withLogging(fn) {
  return (...args) => {
    const result = fn(...args);
    console.log("result:" + result);
    return result;
  };
}
const shout = withLogging((word) => word.toUpperCase());
console.log(shout("pattern"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeAdapt a payment

    Follow the adapter and type what the last line prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function adaptPaymentA(response) {
      return { id: response.payment_id, cents: response.amount_cents, ok: response.status === "paid" };
    }
    const payment = adaptPaymentA({ payment_id: "p1", amount_cents: 1299, status: "paid" });
    console.log(payment.id + ":" + payment.cents + ":" + payment.ok);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticePick a discount

      What number does the code print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const discounts = {
        student: (total) => total * 0.8,
        none: (total) => total,
      };
      console.log(discounts.student(50));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeEmpty the redo list

        Follow the text and the redo list, then type what the last line prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function createEditor(text = "") {
          return {
            text,
            insert(value) { this.text += value; },
            removeEnd(count) { this.text = this.text.slice(0, -count); },
          };
        }
        function insertText(editor, value) {
          return {
            execute() { editor.insert(value); },
            undo() { editor.removeEnd(value.length); },
          };
        }
        const undo = [];
        const redo = [];
        const editor = createEditor("A");
        const command = insertText(editor, "B");
        command.execute();
        undo.push(command);
        undo.pop().undo();
        redo.push(command);
        insertText(editor, "?").execute();
        redo.length = 0;
        console.log(editor.text + " redo=" + redo.length);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFollow a middleware chain

          Type the log in the exact order it is printed.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const log = [];
          const outer = (next) => { log.push("outer before"); next(); log.push("outer after"); };
          const inner = (next) => { log.push("inner before"); next(); log.push("inner after"); };
          outer(() => inner(() => log.push("handler")));
          console.log(log.join(" > "));

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            Structural and behavioral patterns quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which sentence best describes the decorator pattern in JavaScript?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the express shipping strategy print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const shippingStrategies = {
                standard(cart) { return cart.subtotal >= 75 ? 0 : 7; },
                express(cart) { return 12 + cart.items * 2; },
                pickup() { return 0; },
              };
              const cart = { subtotal: 80, items: 2 };
              console.log(shippingStrategies.express(cart));

              Choose an answer to see the explanation.

            3. Question 3 of 7Which pair is most accurate?

              Choose an answer to see the explanation.

            4. Question 4 of 7What is the final command-manager state?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const editor = { text: "Hi" };
              const undo = [];
              const redo = [];
              function insert(value) {
                return {
                  execute() { editor.text += value; },
                  undo() { editor.text = editor.text.slice(0, -value.length); },
                };
              }
              let command = insert("!");
              command.execute();
              undo.push(command);
              undo.pop().undo();
              redo.push(command);
              command = insert("?");
              command.execute();
              undo.push(command);
              redo.length = 0;
              console.log(editor.text + " redo=" + redo.length);

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the middleware onion print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const log = [];
              const outer = (next) => { log.push("outer before"); next(); log.push("outer after"); };
              const inner = (next) => { log.push("inner before"); next(); log.push("inner after"); };
              outer(() => inner(() => log.push("handler")));
              console.log(log.join(" > "));

              Choose an answer to see the explanation.

            6. Question 6 of 7When is a proxy a better fit than a decorator?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which advice keeps these patterns helpful in real projects?

              Choose an answer to see the explanation.

            Key takeaways

            • A decorator wraps a function or object and adds work around it. A proxy stands in front of an object and guards it.
            • An adapter changes outside data into your shape. A facade hides several steps behind one call.
            • A strategy is one of several functions, picked by name while the program runs.
            • A command is an action object with execute() and undo(), so a history can undo and redo it.
            • Middleware is a chain of functions joined by next(). With await next(), the after lines run on the way out.
            One-line summary

            Use a pattern when the code really does its job: wrapping, guarding, translating, picking, undoing, or chaining.

            Next, state management shows how to keep your app's data in one place and change it step by step, with actions and reducers, as the app grows.

            CompleteFrontend Clear concepts. Working examples.