Design Patterns: Structural · Structural Patterns

Facade Pattern

Offering one simple interface in front of a subsystem that needs several coordinated calls.

  • 5 min read
  • 12 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the structural patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Facade is the structural pattern for giving a complicated subsystem one simple front door: a class with a few task-shaped methods (placeOrder, exportVideo) that coordinates many subsystem classes on the client's behalf. It's how service layers, logging APIs and SDKs keep callers from repeating the same fragile choreography. This skill covers the pattern, how to design one, and how to keep it from turning into a god object.

Placing an order touches four services, and they must be called in the right order:

Java

// In the web controller... and the mobile API... and the admin tool...
if (!inventory.reserve(sku, qty)) throw new OutOfStock();
String txn = payments.charge(card, amount);
if (txn == null) { inventory.release(sku, qty); throw new PaymentDeclined(); }
if (!shipping.schedule(sku, address)) { payments.refund(txn); inventory.release(sku, qty); throw new CannotShip(); }
notifications.sendConfirmation(email);

Python

# In the web controller... and the mobile API... and the admin tool...
if not inventory.reserve(sku, qty):
    raise OutOfStock()
txn = payments.charge(card, amount)
if txn is None:
    inventory.release(sku, qty)
    raise PaymentDeclined()
if not shipping.schedule(sku, address):
    payments.refund(txn)
    inventory.release(sku, qty)
    raise CannotShip()
notifications.send_confirmation(email)

Every client repeats this choreography, including the tricky rollback steps. Miss one release() in one copy and stock slowly leaks away.

We'll go from simple to more technical:

  1. The pattern: one task-shaped method instead of a choreography.
  2. Designing a facade, step by step - including rollback.
  3. Where you'll see facades, from SLF4J to API gateways.
  4. Keeping it small and leaving an escape hatch, and choosing among the structural patterns.

The pattern

A Facade is one class with a small, task-shaped interface that coordinates a complicated subsystem on the client's behalf.

Java

class OrderFacade {
    private final Inventory inventory;
    private final Payments payments;
    private final Shipping shipping;
    private final Notifications notifications;

    OrderResult placeOrder(String sku, int qty, Card card, Address address) {
        if (!inventory.reserve(sku, qty)) return OrderResult.failed("out of stock");
        String txn = payments.charge(card, priceOf(sku, qty));
        if (txn == null) {
            inventory.release(sku, qty);
            return OrderResult.failed("payment declined");
        }
        if (!shipping.schedule(sku, address)) {
            payments.refund(txn);
            inventory.release(sku, qty);
            return OrderResult.failed("cannot ship to this address");
        }
        notifications.sendConfirmation(card.email());
        return OrderResult.confirmed(txn);
    }
}

Python

class OrderFacade:
    def __init__(self, inventory: Inventory, payments: Payments,
                 shipping: Shipping, notifications: Notifications):
        self._inventory = inventory
        self._payments = payments
        self._shipping = shipping
        self._notifications = notifications

    def place_order(self, sku: str, qty: int, card: Card, address: Address) -> OrderResult:
        if not self._inventory.reserve(sku, qty):
            return OrderResult.failed("out of stock")
        txn = self._payments.charge(card, self._price_of(sku, qty))
        if txn is None:
            self._inventory.release(sku, qty)
            return OrderResult.failed("payment declined")
        if not self._shipping.schedule(sku, address):
            self._payments.refund(txn)
            self._inventory.release(sku, qty)
            return OrderResult.failed("cannot ship to this address")
        self._notifications.send_confirmation(card.email)
        return OrderResult.confirmed(txn)

Clients now make one call. The subsystem classes don't know the facade exists.

How it works, step by step

  1. Identify the common task clients keep performing (placing an order, exporting a video).
  2. Create a facade class that holds references to the subsystem objects.
  3. Give it methods named after tasks, not after subsystem steps.
  4. Put the ordering and rollback logic in those methods - once.
  5. Leave the subsystems public: clients with unusual needs can still use them directly.

Where you'll see it

  • SLF4J is literally the "Simple Logging Facade for Java": one tiny API in front of Logback, Log4j and others.
  • Spring's JdbcTemplate hides connections, statements, result sets and cleanup behind query(...).
  • In Python, requests.get(url) is a facade over sessions, connection pools and transport adapters, and shutil.copytree hides a whole directory walk of os calls.
  • The service layer of most applications is a set of facades over repositories and integrations.
  • At system scale, an API gateway is a facade in front of many microservices.

Choosing among the structural patterns

If you need to...Reach forWhat it changes
Make an existing class fit the interface your code expectsAdapterChanges the interface; the behavior stays the same.
Add behavior to an object without changing its class or interfaceDecoratorSame interface, extra behavior, and wrappers can be stacked.
Give a complicated subsystem one simple front doorFacade (this lesson)A new, simpler interface over many classes.
Treat a single item and a group of items the same wayCompositeA tree: groups contain items or other groups.
Control access to an object (lazy loading, caching, security, remote calls)ProxySame interface, but it decides when and whether the real object is used.

Common mistakes

MistakeWhat happensDo this instead
One facade for everything (a "god facade")fifty methods every team editsone facade per use case or area
Forbidding direct access to the subsystemadvanced needs bloat the facadekeep the subsystem usable directly
Business rules inside the facadedomain logic scattered into orchestrationkeep rules in domain objects; the facade orchestrates
Rolling back in the wrong order, or not at allleaked stock, charges without orderscompensate completed steps in reverse order
Calling payment or email APIs inside a database transactiona charge survives the rollbackcommit first, then call out (or use an outbox)

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Do the subsystem classes know that a facade exists?

Show answer

No. A facade is one-directional: it calls the subsystem, the subsystem never refers back to it, and clients with unusual needs can still use the subsystem directly.

2. Shipping fails after the payment went through. What should an order facade do?

Show answer

Undo the completed steps in reverse order - refund the payment, then release the reserved stock - and report the failure. That compensation logic belongs in the facade, once.

3. Your facade has grown to 60 methods that every team edits. What next?

Show answer

Split it by use case (OrderFacade, ReturnsFacade, ...). A facade should simplify one area, not become the application's single point of contact.

Key takeaways

  • A facade gives clients one simple, task-shaped method instead of a choreography of subsystem calls.
  • Ordering and rollback logic live in one place instead of in every client.
  • The subsystems don't know about the facade, and clients may still use them directly.
  • Split a growing facade by use case before it becomes a god object.
  • Adapter changes one interface to fit; Facade simplifies many.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You need to...Reach for
One simple entry point to a subsystemFacade
A different simple API per client typeBackends for Frontends
Routing, auth and rate limits in front of servicesan API gateway
Peers that coordinate through a hubMediator
Multi-step operations that must survive crashespersisted saga state or a workflow engine

Snippets worth knowing by heart

Java

OrderResult placeOrder(String sku, int qty, Card card, Address address) {
    Deque<Runnable> undo = new ArrayDeque<>();
    try {
        inventory.reserve(sku, qty);            undo.push(() -> inventory.release(sku, qty));
        String txn = payments.charge(card, priceOf(sku, qty));
        undo.push(() -> payments.refund(txn));
        shipping.schedule(sku, address);        // the last reversible step
        notifications.sendConfirmation(card.email());   // irreversible: last
        return OrderResult.confirmed(txn);
    } catch (StepFailed e) {
        while (!undo.isEmpty()) undo.pop().run();       // reverse order
        return OrderResult.failed(e.getMessage());
    }
}

Python

def place_order(self, sku, qty, card, address) -> OrderResult:
    undo = []
    try:
        self._inventory.reserve(sku, qty)
        undo.append(lambda: self._inventory.release(sku, qty))
        txn = self._payments.charge(card, self._price_of(sku, qty))
        undo.append(lambda: self._payments.refund(txn))
        self._shipping.schedule(sku, address)
        self._notifications.send_confirmation(card.email)   # irreversible: last
        return OrderResult.confirmed(txn)
    except StepFailed as e:
        for compensate in reversed(undo):
            compensate()
        return OrderResult.failed(str(e))

The traps

  • A god facade - split by use case.
  • No escape hatch - leave the subsystem usable.
  • Business rules in the facade.
  • External calls inside DB transactions.

Staff-level points, one line each

  • A facade with rollback is an in-process saga: compensations in reverse (reserve, charge, refund, release).
  • Compensations must be idempotent and retryable - a timed-out refund is retried with the transaction id as its key.
  • Order steps by reversibility; the first irreversible step is the pivot - after it, recover forward.
  • A crash mid-saga leaks state: expiring reservations, persisted saga state, or a workflow engine.
  • A charge inside a rolled-back transaction survived: 0 orders, 1 charge - commit first or use an outbox.
  • Backends for Frontends: one facade per client type; gateways stay free of business orchestration.
  • Facade is one-way; a mediator is called through by peers that know it.

Say it in an interview

  • "Clients make one call; the facade owns the ordering and the rollback."
  • "If shipping fails we refund, then release the stock."
  • "The email goes last - it's the one step we can't undo."
  • "We don't call the payment API inside the transaction; we commit, then charge, with compensation."

Practice

This skill comes with 12 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Facade Pattern free

One-Call Checkout With Rollback

MediumProCoding problemJava · Python

Placing an order touches three services, and when a later step fails the earlier ones must be undone. Instead of every client repeating that choreography, put it in OrderFacade.placeOrder(item, qty, card, address):

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Facade Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime