Once you've built a handful of workflows, you'll notice the same pattern repeating - the same "look up this customer," "format this into Slack markdown," or "log this to a spreadsheet" sequence, copy-pasted into workflow after workflow. Sub-workflows exist to end that.
The Execute Workflow node
Any workflow can call another workflow as if it were a single node, using the Execute Workflow node. You pass data in, the called workflow runs to completion, and its output comes back to the calling workflow - exactly like calling a function in a programming language. The called workflow doesn't need its own trigger node when it's only ever invoked this way; it just needs a starting point that accepts input.
What makes something a good candidate for a sub-workflow
Anything you've built more than twice, identically. Common candidates: a "format and send Slack alert" sequence, a "look up or create a CRM record" flow, a "validate and normalize this incoming payload" step, or a "log this event with standard fields" pattern. If you catch yourself duplicating five nodes into a third workflow, that's the signal to extract it.
Passing data in and out cleanly
Design the sub-workflow's expected input like a small API contract - decide what fields it needs (e.g., customerId, message) and document that at the top with a Sticky Note. On the calling side, map exactly those fields into the Execute Workflow node's input. This discipline pays off the moment you have more than two or three people building workflows against the same shared sub-workflow.
Synchronous vs "fire and forget"
By default, Execute Workflow waits for the sub-workflow to finish and returns its result before continuing - useful when you need that output for the next step. Some setups don't need to wait (e.g., a logging sub-workflow that doesn't affect the main flow's outcome); check your version's options for whether execution can continue without waiting, since this affects both speed and error propagation.
Versioning a shared sub-workflow carefully
Because a change to a widely-used sub-workflow affects every workflow calling it, treat changes to shared sub-workflows the way you'd treat changes to a shared library - test in a copy first, or use n8n's built-in workflow versioning/history if your instance supports it, before editing the live version that other workflows depend on in production.