· via dev.to (home feed)
Inside n8n's execution engine: how the 208k-star workflow tool actually runs your workflows
A dev.to deep dive explains how n8n, the 208k-star fair-code alternative to Zapier, schedules and executes workflows with a single stack, a waiting map and one loop.
A self-hosted Zapier alternative with a code escape hatch
n8n is often described as an open-source Zapier, and a recent deep dive on dev.to argues the label is both useful and misleading. Zapier is a hosted product you rent; n8n is a Node.js application you run yourself, pointed at your own database and owned end to end. There is no per-task metering and no vendor lock-in, and according to the article a VPS in the five-to-ten-dollar range can carry a surprising amount of load. The project has gathered roughly 208,000 GitHub stars under its fair-code licence.
The author's real focus, though, is not the pitch but the machinery: what happens between clicking Execute Workflow and the green checkmarks appearing on the canvas. That behaviour — scheduling nodes, synchronising joins, keeping runs cancellable, emitting lifecycle hooks — lives in roughly 1,250 lines of TypeScript.
One serializable object and a single loop
According to the article, n8n does not spawn a thread per node. It builds a single serializable run object, IRunExecutionData, which carries the in-flight stack of nodes waiting to run, a waiting map for partially satisfied inputs, metadata, and final results. The same object travels from the editor to the server, into the engine, optionally across a Redis queue to a worker process, and back again.
The engine — WorkflowExecute in the core package — seeds the stack with the start node and then loops: take the front entry, run the node, record the result, enqueue downstream nodes. No hidden thread pool, no magic graph walker. Just a stack and a waiting map.
Execution order comes down to one line
The article singles out executionOrder as the most important workflow setting, because it changes one line in the scheduler. Version v0 pushes new work onto the tail, so older work runs first and the walk feels breadth-first: all siblings at depth one finish before depth two starts. Version v1 unshifts new work to the front, so the current branch keeps running and the walk feels depth-first.
The setting changes scheduling, not the graph. In v1, sibling ordering still follows canvas position — top-left first — but only among siblings that are actually ready. The canvas is not the execution graph; it merely shapes ordering where the scheduler has freedom.
Joins, empty branches and loops
A node with two inputs does not fire when the first branch arrives. The engine parks partial data in the waiting map with per-input placeholders and enqueues the node only once every required input is non-null. After the main stack empties, a sweep pulls forward any waiting node that now has enough data — even if optional inputs stayed empty — so a join can still complete after one branch dies early.
Empty output normally ends a branch, the article explains, unless the node sets alwaysOutputData to true, in which case the engine fabricates a single empty item to keep downstream nodes running. Cycles get no special treatment: a node loops only if it schedules more work for itself, as the batch-splitting node does by storing loop state in its own context, while a loop guard stops accidental repeats when the same node and run index return without progress.
Queue mode: same engine, more processes
In standalone mode the main process executes workflows directly. In queue mode, with EXECUTIONS_MODE set to queue, the main process enqueues a job into Redis via BullMQ and returns immediately; a worker dequeues it, reconstructs the same run object, runs the identical engine loop and writes results back to PostgreSQL. Workers are stateless and can be scaled horizontally on queue depth, and code nodes run in isolated task-runner sidecars — V8 isolates via isolated-vm for JavaScript, separate processes for Python — communicating over WebSocket RPC.
Underneath sits a three-layer data model: items flowing between nodes, per-run task records appended to each node's history, and the execution object that bundles results together with live machine state. That bundling, the author notes, is how n8n can hand an execution to a queue worker or pause it at a Wait node and resume later.
Why it matters
Automation platforms succeed or fail at the edges: the join that must wait, the branch that dies silently, the loop that never terminates. The dev.to walkthrough shows that n8n's edge behaviour is governed by a small, legible set of rules — a stack, a waiting map, a couple of settings — rather than opaque machinery. For teams weighing it against Zapier, that legibility is the real argument: when behaviour surprises you, the answer sits a few hundred lines of TypeScript away, or behind a Code node if you would rather fix it in JavaScript or Python than read the engine at all.
- #n8n
- #workflow-automation
- #open-source
- #self-hosting
- #node-js