Module 5: Dependencies Between Workflows

2. Orchestration vs choreography

Description

By the end of this lesson you will be able to tell apart the two fundamental ways of coordinating several workflows — orchestration, where a director flow calls the others and waits for their results, and choreography, where each flow reacts on its own to the events that matter to it, with no director — and you will be able to choose which one fits a concrete situation, knowing what you gain and what you lose with each. You will also know how to build each one in n8n: orchestration with the Execute Sub-workflow node, and choreography with events between webhooks or a table that gets polled.

This matters because it is the first design decision of every multi-flow system, and it is one of those decisions that, if you make it without meaning to, follows you around for months. Almost everyone starts orchestrating without realizing it — you connect an Execute Sub-workflow and that's it — and that is fine for many cases. But there are situations where orchestration creates exactly the cascade we saw in lesson 1, and where choreography avoids it. Knowing how to name the two, and knowing why you chose the one you chose, is what separates a system that grows in an orderly way from one that tangles itself up.

Connection to the module: lesson 1 gave you the Cumbre system and the three disasters. This lesson is the first decision about that system: how the four workflows talk to each other. The two forms you see here are the two ways of drawing the arrows of the graph you will build in lesson 3 — an orchestration arrow means "this one calls this one and waits"; a choreography arrow means "this one emits an event and that one reacts." The outbox pattern from lesson 6 is going to turn out to be, among other things, the way to get the best of choreography (decoupling) without losing safety. So this lesson plants the vocabulary the rest of the module builds on.

Two ways for several pieces to work together

Think of two ways a group of musicians can play a coordinated song.

The first is an orchestra with a conductor. There is a person up front with a baton. The violins do not come in whenever they feel like it: they come in when the conductor gives them the cue. The conductor has the complete score in their head, knows what comes after what, and marks each section's moment. If you want to know what is happening in the piece, you watch the conductor: they are the single point where the whole plan lives. The advantage is total control and visibility — there is one single place to understand the choreography. The disadvantage is that if the conductor makes a mistake or passes out, the whole orchestra stops, because no one else has the plan.

The second is a jazz band improvising. There is no conductor. The bassist starts, the drummer listens and comes in with a rhythm that fits, the saxophone hears both and joins in. No one has the whole piece written down; each musician reacts to what they hear from the others and knows what their role is. The advantage is that it is resilient: if the sax drops out for a moment, the band keeps going, because no one depended on a central cue. The disadvantage is that no one has the full picture — to understand what is going to happen you have to know each musician's reaction rules, there is no single score to look at.

Those two forms are, exactly, orchestration and choreography. In orchestration, a director flow has the plan and tells each piece when to act. In choreography, there is no director: each piece reacts to the events it perceives, following its own rule. Both coordinate; they do it in opposite ways, with opposite costs. Let's look at them one by one.

Orchestration: a director that calls each piece

In orchestration, a central workflow — the director — has the full sequence and calls each sub-workflow in the order that applies, waiting for each one's result before moving on.

Its anatomy in n8n is the Execute Sub-workflow node. You already know it from Module 3: it is the node one flow uses to call another. Its default behavior is what makes orchestration possible: it waits for the sub-workflow to finish and receives its result before moving to the next node. In the node's panel this is the Wait for Sub-Workflow Completion option, which comes turned on. While order-triage waits on check-credit, it does not move forward; when check-credit returns its response — "the customer has $8,000 of credit available" — order-triage reads it and decides what to do with it.

For Cumbre, an orchestrating order-triage would look like this:

# Workflow: order-triage (the director)

Webhook (order comes in)
  → AI Agent (classifies the order)
  → Execute Sub-workflow: check-credit          ← waits for the result
  → IF (does it have credit?)
      ├── yes → Execute Sub-workflow: inventory-sync   ← waits
      │        → HTTP Request: register in CRM
      └── no → Execute Sub-workflow: issue-refund     ← waits
               → Send Email: insufficient credit notice

Read that flow slowly, because it has orchestration's signature: the complete plan lives in order-triage. If you want to know in what order things happen, what calls what, and what gets decided where, you read it all in a single workflow. check-credit, inventory-sync, and issue-refund know nothing about the plan: each one receives an assignment, resolves it, and returns its result. They are performers; the director is order-triage.

Orchestration's advantages are three, and they are strong:

  1. Visibility. The plan is in one single place. A new colleague opens order-triage and understands the whole system. They do not have to piece together clues from four workflows.
  2. Control over order. Because the director waits for each result before moving on, the sequence is exactly the one you wrote. check-credit runs before inventory-sync, always, because that is how the flow is laid out.
  3. Decisions based on results. The director receives each piece's result and can decide the next step based on it. "If credit is insufficient, do not discount inventory, send the notice." That conditional logic lives naturally in the director.

Its disadvantages are the flip side of its advantages:

  1. Coupling. The director has to know about each piece: what it's called, what contract it has, in what order it goes. A change in check-credit can force you to touch order-triage. The pieces are tied to the director.
  2. The director is a single point. If order-triage goes down, nothing runs, because no one else has the plan. It is the conductor passing out and stopping the orchestra.
  3. Cascade risk. Because the director waits for each piece, a slow sub-workflow blocks it, and that block propagates backward — exactly the cascade from lesson 1. If check-credit takes ten seconds, order-triage takes ten extra seconds, and orders pile up at the entrance.

Choreography: pieces that react to events, with no director

In choreography, there is no director. Each workflow reacts on its own to an event — something that happened — and does its part without waiting for anyone or being waited on. The plan does not live in any central place: it lives spread out across each piece's reaction rules.

The anatomy in n8n is different, and here an honest clarification is in order: n8n does not have a native publish/subscribe event bus like the ones big systems have. So choreography in n8n gets built with the pieces it actually has, and there are two common forms:

Form A — events by webhook. When order-triage finishes classifying an order, instead of calling the other workflows, it emits an event: it makes an HTTP Request to each interested workflow's webhook URL, or to a single point, saying "this happened: order.created, with this data." inventory-sync has its own Webhook node listening for that event and starts on its own. So does check-credit. order-triage does not wait for either one: it emitted the event and moved on with its own business.

Form B — an events table that gets polled. order-triage writes a row into a table — "event: order.created, order_id: ORD-2041" — and every interested workflow has a Schedule Trigger that checks that table every so often, picks up the new events that concern it, and processes them. No one calls anyone directly; the meeting point is the table. This form, as you will see, is a first cousin of the outbox pattern from lesson 6.

For Cumbre, a choreographed version would look like this:

# Workflow: order-triage (no longer a director, now an emitter)
Webhook (order comes in)
  → AI Agent (classifies)
  → HTTP Request: emit "order.created" event   ← waits for no one
  → responds to the vendor and finishes

# Workflow: inventory-sync (reacts on its own)
Webhook: listens for "order.created"
  → discounts inventory

# Workflow: check-credit (reacts on its own)
Webhook: listens for "order.created"
  → checks credit
  → if insufficient, emits another event: "credit.rejected"

# Workflow: issue-refund (reacts to another event)
Webhook: listens for "credit.rejected"
  → issues the refund

Notice what changed. order-triage no longer knows inventory-sync, check-credit, or issue-refund exist. It only knows how to emit the "order.created" event. And issue-refund does not know order-triage exists: it only knows that when "credit.rejected" happens, it issues the refund. The pieces communicate through events, not through direct calls. Each one is a jazz musician reacting to what they hear.

Choreography's advantages:

  1. Decoupling. order-triage does not know the pieces that react. You can add a fifth workflow that also listens for "order.created" — say, one that sends the order to analytics — without touching order-triage at all. The pieces are not tied to each other.
  2. Resilience. Because no one waits on anyone, a slow workflow does not block the others. If inventory-sync is slow, check-credit already ran anyway, because the two reacted to the same event independently. There is no director that can hang and stop everything.
  3. Independent scaling. Each piece runs at its own pace. The one under heavy load can be reinforced without touching the others.

The disadvantages, the flip side again:

  1. No one has the full picture. To understand what happens when an order comes in, you have to know the reaction rules of four workflows and how their events chain together. There is no single place to read. Debugging "why wasn't the refund issued?" means following the trail event by event.
  2. Order is harder to guarantee. Because the pieces react in parallel and without waiting on each other, ensuring one thing happens before another takes extra work — exactly lesson 5's subject.
  3. Deciding based on results is awkward. In orchestration, the director receives check-credit's result and decides. In choreography, check-credit has to emit another event with its result so that someone reacts. Conditional logic gets spread across chains of events, which are harder to follow than an IF node in a single flow.

The two, side by side

It is worth putting the differences in a table, because they are concrete decisions, not a matter of style:

OrchestrationChoreography
Who has the planThe director (order-triage), in one placeNo one; spread across each piece's rules
How pieces get calledExecute Sub-workflow, waiting for the resultEvents: webhook to webhook, or a table that gets polled
Visibility of the full flowHigh: it reads in one workflowLow: has to be rebuilt by following events
CouplingHigh: the director knows each pieceLow: the pieces do not know each other
Resilience to a slow pieceLow: blocks the director (cascade risk)High: no one waits on anyone
Order controlEasy: the sequence is written downHard: takes extra work (lesson 5)
Deciding on a resultNatural: the director reads and decidesAwkward: another event has to be emitted
Adding a new pieceThe director has to be touchedThe new piece subscribes to the event, nothing else touched

Neither one is "the good one." They are two tools with opposite profiles, and choosing well is a matter of knowing which one weighs more in your case.

When each one fits

The practical rule, distilled from the table:

Orchestrate when you need order, results to decide on, and visibility. If the logic is "do A, look at the result, if it says this do B, otherwise do C," that is a director. Cumbre's chain — check credit, and based on the credit decide whether to discount inventory or send a notice — is naturally orchestrated, because there is a decision that depends on a result. Forcing that into choreography fills you up with "credit.approved" / "credit.rejected" events that are harder to follow than an IF.

Choreograph when you want to decouple and the pieces do not depend on each other's results. If the "order.created" event has to trigger three things that do not need each other — discounting inventory, sending to analytics, notifying the sales team — that is natural choreography: all three react to the same event, none waits on the others, and adding a fourth touches nothing. Forcing that into orchestration ties three independent pieces to a director that contributes nothing more than being a bottleneck.

And something almost no one says at the start: most real systems are a mix. You do not have to pick one for everything. Cumbre can orchestrate the credit-decision-refund chain, because there is a sequence with decisions there, and at the same time emit an "order.created" event that triggers, in choreography, the inventory discount and the sales notification, because those do not depend on anything. The question is not "do I orchestrate or choreograph my system?" but "does this specific piece need order and decision (orchestrate) or decoupling (choreograph)?" It is decided edge by edge in the graph.

Worked example: Cumbre's credit chain, orchestrated

Let's build the orchestrated part of the system — the credit-decision chain — so you can see the concrete mechanism. It is the part where orchestration is clearly the right choice, because there is a decision that depends on a result.

Step 1 — The check-credit sub-workflow with its contract. check-credit is a separate workflow that starts with an Execute Sub-workflow Trigger. In that trigger you declare the input contract with Define using fields below, as you learned in Module 3: it receives an order_id, a customer_id, and an amount. Its job is to check available credit and return an answer. Its last node — an Edit Fields — defines what it returns:

{
  "order_id": "ORD-2041",
  "credit_available": 8000.00,
  "credit_ok": true
}

Remember from Module 3: the sub-workflow's last node is the one that returns the data to the Execute Sub-workflow node that called it. check-credit is a pure read: it checks and returns, it changes nothing.

Step 2 — The director calls and waits. In order-triage, after the AI Agent, you place an Execute Sub-workflow node configured like this:

# Node: Execute Sub-workflow — call check-credit
Source: Database
Workflow: check-credit
Wait for Sub-Workflow Completion: on   ← the director waits for the result
Mode: Run once for each item
Workflow Inputs:
  order_id    = {{ $json.order_id }}
  customer_id = {{ $json.customer_id }}
  amount      = {{ $json.amount }}

What to expect. When you run order-triage with order ORD-2041, you will see the Execute Sub-workflow node sit "running" for a moment while check-credit does its job, and then fill in with the response: the Execute Sub-workflow output item carries the credit_available and credit_ok fields the sub-workflow returned. That is Wait for Sub-Workflow Completion in action: the director waited and received the result. If you open the execution, you will see check-credit show up as a linked execution, not as part of order-triage's own execution — they are two separate executions, connected by the call.

Step 3 — The director decides on the result. Now that order-triage has credit_ok, you place an IF node that decides the path:

# Node: IF — is there enough credit?
Condition: {{ $json.credit_ok }} equals true

  true branch  → Execute Sub-workflow: inventory-sync  (discounts stock)
  false branch → Execute Sub-workflow: issue-refund    (issues the notice/refund)

This is the reason this part gets orchestrated rather than choreographed: the decision depends on check-credit's result. The director reads it and branches. In choreography, you would have to make check-credit emit a different event depending on the result, and have inventory-sync and issue-refund react to different events — more pieces, harder to follow, for logic that here fits in one IF.

An honest note about this orchestrated chain. Notice that inventory-sync and issue-refund are effects, and we are calling them inline, in the director's own flow. That works, but it leaves a crack: if order-triage crashes right after discounting inventory but before registering in the CRM, and then gets retried, it is going to discount inventory again. Orchestration by itself does not solve that. What solves it is making those effects idempotent (Module 2) and, better still, pulling them out of the director's flow with the outbox pattern (lesson 6). For now, hold on to the orchestration mechanics; we are going to patch the crack in the coming lessons.

A nuance that avoids the most common mistake: synchronous is not the same as orchestrated

There is a confusion worth untangling, because it trips up a lot of people. "Orchestrated" and "synchronous" sound like the same thing, and they are not.

Synchronous vs. asynchronous is a question about timing: does the caller wait for the response (synchronous) or does it keep going without waiting (asynchronous)? Execute Sub-workflow with Wait for Sub-Workflow Completion turned on is synchronous; with that option turned off, it is asynchronous — it fires the sub-workflow and keeps going without waiting.

Orchestration vs. choreography is a question about who has the plan: a central director (orchestration) or rules spread across pieces (choreography)?

Those are two different axes. You can have asynchronous orchestration: a director that fires several pieces without waiting for them and collects them later — that is exactly lesson 4's fan-out. And choreography is almost always asynchronous, because no one waits on anyone. When the rest of the module says "asynchronous," we mean the timing axis; "choreography" is the plan axis. Keeping them separate in your head saves you confusing arguments.

Common mistakes

Choreographing logic that is actually a sequential decision (conceptual). What happens: someone, excited about decoupling, builds the whole Cumbre chain with events: "order.created" triggers check-credit, which emits "credit.checked," which triggers a workflow that decides, which emits "decision.made"... and ends up with six workflows and five event types for logic that was "check the credit and branch." Why it happens: choreography sounds more modern and "decoupled," and gets applied as if it were always better. How to detect it: if understanding what happens to an order requires opening five workflows and following a chain of events that represents a single if/else decision, you over-choreographed. How to fix it: where there is a decision that depends on a result, orchestrate — a director with an IF is clearer and easier to debug than a chain of events; save choreography for where there really are independent pieces that do not need each other.

Orchestrating independent things and creating a bottleneck (conceptual). What happens: order-triage calls, in sequence, waiting for each one, inventory-sync, an analytics workflow, and a notification one, even though all three are independent and none needs the others' results. The order takes as long as the sum of the three, and if one goes down, the other two do not run. Why it happens: it is the easiest thing to build — you connect three Execute Sub-workflow nodes in a row — and in the demo, with everything fast, the cost does not show. How to detect it: if you have several calls in sequence where none uses the previous one's result, you are serializing them without reason. How to fix it: if they really are independent, either trigger them in choreography (emit an event and each one reacts) or parallelize them with lesson 4's fan-out; do not line them up waiting on each other without a reason.

Forgetting that Execute Sub-workflow waits by default (practical). What happens: someone builds an orchestrated chain expecting each piece to run in parallel, but because Wait for Sub-Workflow Completion comes turned on, the pieces run in a row, one after another, and the total time is the sum — not the max. Or the reverse: someone turns that option off "to make it faster" and then their director tries to read a result that never arrived, because it never waited. Why it happens: the option's name and its default value are not always top of mind when building the flow. How to detect it: if your director "reads the result" of a sub-workflow, that option has to be on; if you are not reading any result and just firing, consider turning it off. How to fix it: decide explicitly, for every Execute Sub-workflow, whether you need the result (wait) or just need to fire (do not wait), and check the option's value in the panel — its exact label can vary between versions, so confirm it on your own instance.

Believing choreography in n8n is "free" like in a system with an event bus (practical). What happens: someone reads about event-driven architectures in large systems and assumes n8n has a native publish/subscribe bus where you emit an event and "magically" everyone interested reacts. They get frustrated when they cannot find it. Why it happens: event theory is almost always taught with tools that do have that bus, and n8n does not have one natively. How to detect it: if you look for a generic "publish event" node and it does not appear, this is it. How to fix it: in n8n, choreography gets built with the pieces that actually exist — an HTTP Request from one webhook to another, or an events table that each workflow polls with a Schedule Trigger; the second form, the table, is also the basis of lesson 6's outbox pattern, so it is not a detour: it is the path.

Exercises

Exercise 1 — Classify each edge. Cumbre's system has these four relationships. For each one, say whether orchestration or choreography fits and why, in one sentence:

(a) order-triage checks check-credit and, based on the credit, decides whether to discount inventory or send a notice. (b) When an order comes in, it needs to be discounted from inventory, sent to an analytics dashboard, and the sales team needs to be notified; none of the three needs the others' results. (c) issue-refund should only run if check-credit determined there is no credit. (d) A fifth process, new, wants to log every order to an audit file, without affecting anything else.

See solution

(a) Orchestration. There is a decision that depends on a result — "based on the credit" — and that is exactly what a director does well: calls check-credit, reads the result, and branches with an IF. Choreographing it would fill the system with events to represent a simple if/else.

(b) Choreography. Three independent pieces that react to the same fact ("an order came in") and do not need each other. You emit an "order.created" event and each one reacts on its own; none blocks the others, and adding a fourth touches none of the three existing ones.

(c) Orchestration. issue-refund depends on check-credit's result. That result-dependency is orchestration's signature: the director checks the credit and, only if it is insufficient, calls issue-refund.

(d) Choreography. The textbook case of decoupling: a new process that wants to learn about a fact without affecting anything. It subscribes to the "order.created" event and that's it; there is no need to touch order-triage or any other piece. Orchestrating it would force one more call into the director, for something that is none of its business.

Why this works: notice that the same system uses both forms. (a) and (c) are orchestrated because there are decisions based on results; (b) and (d) are choreographed because they are independent pieces reacting to a fact. The decision is made edge by edge, not for the whole system at once.

Exercise 2 — Predict the cascade. In Cumbre's orchestrated version, order-triage calls, in sequence and waiting, check-credit, inventory-sync, and issue-refund. One day, the warehouse system inventory-sync uses gets slow and each call takes 15 seconds instead of 1. Describe what happens to the whole system, then say how choreography would have changed the outcome.

See solution

In the orchestrated version: because order-triage waits for inventory-sync before moving on, every order now takes 15 extra seconds. While order-triage is blocked waiting, it does not handle the next order, so orders start piling up at the entrance. The vendor firing the webhook, not getting confirmation in time, retries, feeding more orders into the queue. One slow service — the warehouse — ended up stalling reception of every order and multiplying the load. It is the cascade from lesson 1, caused by the director waiting.

With choreography: order-triage would have emitted the "order.created" event and moved on without waiting for anyone. inventory-sync, reacting on its own, would have fallen 15 seconds behind — its internal queue grows — but that would not have blocked order-triage or check-credit, which ran at their own pace. The problem would have stayed contained inside inventory-sync instead of propagating. The slow warehouse is still a problem, but a local problem, not a cascade.

Why this works: this exercise shows the concrete cost of synchronous orchestration — the director inherits the slowness of the slowest piece — and why choreography's decoupling is real protection against a cascade. It does not mean choreography is always better: it means that for independent pieces, decoupling buys resilience.

Exercise 3 — Design the choreographed part. Cumbre wants that, when an order comes in, in addition to the orchestrated credit chain, two independent things fire in choreography: logging the order to an analytics dashboard and notifying the sales team if the amount exceeds $50,000. Design, with boxes and arrows, what that choreographed part would look like: what event gets emitted, who listens for it, and what each one does.

See solution

A reasonable form:

# order-triage, in addition to the orchestrated chain, emits an event
order-triage
  → ...orchestrated credit chain...
  → HTTP Request: emit "order.created" event   ← waits for no one

# Two independent workflows react to the same event
analytics-logger
  Webhook: listens for "order.created"
    → logs the order to the analytics dashboard

sales-notifier
  Webhook: listens for "order.created"
    → IF: amount > 50000?
        yes → Send Email / message to the sales team
        no → (does nothing)

The key points of a good design: order-triage emits one single event and does not know who listens to it — that is the decoupling; the two workflows react independently, so if analytics-logger goes down, sales-notifier runs anyway; and the amount decision lives inside sales-notifier, not in order-triage, because it is its own business. If tomorrow you want a third reactor — say, one that updates an external CRM — you subscribe it to the same event without touching any of the above.

A note on correctness that gets deepened later: if that event could arrive twice (because order-triage fired twice), each reactor must be idempotent to avoid duplicating its effect — notifying twice, logging twice. The sales notification, for example, should check the ledger for whether it already notified for that order_id. That is the bridge to the rest of the module.

Why this works: you correctly separated what is orchestrated (the credit chain, with its decision) from what is choreographed (two independent reactors to an event). That criterion — order and decision get orchestrated, independent pieces get choreographed — is the one you will apply every time you design a multi-flow system.

Summary and next step

In this lesson you separated the two ways of coordinating several workflows. Orchestration puts the plan in a central director — in Cumbre, order-triage — that calls each piece with Execute Sub-workflow, waits for its result thanks to Wait for Sub-Workflow Completion, and decides the next step; it gives you visibility, control over order, and decisions based on results, in exchange for coupling, a single point of failure, and cascade risk. Choreography removes the director: each piece reacts on its own to events — by webhook or by a table that gets polled; it gives you decoupling, resilience, and independent scaling, in exchange for losing the full picture, making order harder, and making decisions based on results awkward. And you saw that almost every real system is a mix: it gets decided edge by edge, orchestrating where there is order and decision, choreographing where there are independent pieces. Finally, you separated two axes that get confused: synchronous/asynchronous is about timing, orchestration/choreography is about who has the plan.

Before moving on to lesson 3 you should be able to: explain the difference between orchestration and choreography in one sentence; say which Execute Sub-workflow option makes the director wait for the result; and, given a case, decide which of the two fits and why.

Now that you know the arrows between workflows can be of two kinds — calls that wait or events that react — lesson 3 teaches you to draw them all together: the system's dependency graph. You will learn to put on paper who depends on whom, to mark each edge with its type, and — most valuably — to read in that drawing how far a failure will propagate before it happens. It is the tool that turns "I'm not quite sure what's happening in there" into a diagram you can reason about.

Resources

  • Execute Sub-workflow node — n8n Docs — the director's node: the Wait for Sub-Workflow Completion option, the Run once for all items / Run once for each item modes, and Workflow Inputs mapping. Check the exact label of the wait option in your version.
  • Execute Sub-workflow Trigger — n8n Docs — the sub-workflow's trigger, where the contract gets declared with Define using fields below and where it is worth remembering that the last node defines the response.
  • Webhook node — n8n Docs — the node that makes event-based choreography possible: every reacting workflow has its own webhook listening.
  • Schedule Trigger — n8n Docs — the trigger for table-based choreography: every workflow checks the events table every so often and picks up what concerns it. It is the basis of lesson 6's outbox pattern.
  • Sub-workflows — n8n Docs — the overview of how a system is made up of several workflows that call each other.