A workflow engine will require every handler to return its next step as a value rather than call it — what does publishing that step type commit extending teams to?
answer
- the style spreads to every caller
- one type, many teams
- handlers may no longer call directly
- the loop is a single control point
- the failure path shows the loop
basics
~20 sIt makes the returning style viral across the whole extension surface: handlers may no longer use ordinary calls to continue a chain, the step type becomes public API you cannot change quietly, and failures show the driver loop instead of the logical path.
solid answer
~40 sThe step type stops being an implementation detail the moment outside handlers must produce it. Every extender writes in the returning style, and a handler that continues a chain with a direct call silently reintroduces the depth the engine was built to remove — the engine cannot detect that in general. Diagnostics change too: a failure's call path shows the loop and one step, so the engine must record its own trail. In exchange, every step passes through one place, so depth budgets, metering, cancellation and audit become engine features rather than rules each team must obey. The bet is whether chain length is genuinely decided by data nobody controls.
go deeper
The point to carry away is that a rule like this is not just about one function: once outside teams must produce the value, the way of writing spreads to all of their code too.
Be able to name both directions — what extenders give up, which is the ordinary call between steps, and what the engine gains, which is one place every step passes through.
Show that you would build the replacement for what is lost: a step trail recorded by the loop, and a test chain deeper than anything the previous design survived. Say plainly that the engine cannot enforce the style itself.
Make the bet explicit. State the evidence that depth is decided by data you do not control, price the imposition across every extending team, and offer the partial adoption at the unbounded boundary as the cheaper alternative you considered.
## What the contract actually says The engine stops offering `write a handler that does the work and calls what comes next`. It offers `write a handler that does its own work and returns a value naming what comes next`. That value — the step type — is now part of the published surface: outside code constructs it, the engine consumes it, and the driver loop is the only thing that ever invokes a step. Everything below follows from that one sentence. ## What it takes from the teams that extend you - **The ordinary call as a control-flow tool inside a chain.** Continuing a chain must go through a returned value. Sequencing, branching and looping between steps are expressed as which step is named next, not as code that calls code. - **The guarantee they were sold, if they ignore the style.** A handler that continues by calling another handler directly nests exactly as before. The engine has no general way to detect this — it sees only a value it was handed — so the property is upheld by review and by a test chain longer than any depth the fixed region ever held. - **A readable failure path.** When a step throws, the path leading to it is the loop, not the chain: the logical route through fifty thousand approvals is not on it. The engine has to record its own step trail, and that trail is now something extenders depend on. - **Quiet evolution of the type.** A step type only the engine built could gain a variant whenever convenient. One that outside teams both build and inspect cannot: adding a variant is a change every extender may have to handle. ## What the single funnel gives you - **One control point.** Depth budgets, per-chain step limits, metering, cancellation and fairness between concurrent chains are written once in the loop rather than specified as rules each team must remember. - **Steps you can see before they run.** A returned step is a value, so it can be logged, counted, replayed in a test or rejected by policy — none of which is possible when the continuation is a call that has already happened. - **Handlers that test alone.** A handler that returns its next step can be invoked in a test and its result inspected, with no engine and no chain around it. That is a genuine ergonomic gain and worth saying out loud, because the rest of the change reads as cost. ## The comparison, stated plainly | | handler calls the next step | handler returns the next step | |---|---|---| | depth for a chain of n steps | grows with n | one step at a time | | who may invoke a step | any handler | only the driver loop | | where a limit or a meter lives | in every handler, by convention | in the loop, once | | what a failure path shows | the logical chain | the loop and the current step | | cost of adding a step variant | internal change | change visible to every extender | | per-step overhead | a call | a call, an allocation, a loop turn | ## The questions to answer before committing 1. **Is the depth real?** If the engine's own design bounds a chain to a few dozen steps, you are imposing a style on every team for a failure that cannot occur. If length comes from customer data, it is not a style question at all. 2. **How many teams extend this, and how experienced are they?** The returning style is learnable in an afternoon and forgettable in a week; the cost is spread across everyone, while the benefit is concentrated on the rare deep chain. 3. **What replaces the failure path?** If the answer is `nothing yet`, that is the first thing to build, not an afterthought — diagnosability is what the change spends. 4. **Can the type be kept small and closed?** A step type with two readings — more work, or finished — is cheap to learn and cheap to keep stable. One that grows a variant per feature becomes a shared vocabulary you can never revise. 5. **Is a partial adoption honest?** Requiring the style only at the boundary whose length is unbounded, and leaving bounded inner walks as ordinary recursion, keeps most of the benefit for a fraction of the imposition — provided the boundary is documented and tested. ## How to make the bet cheaper - Ship a helper that lifts an ordinary function into a step, so the common handler is one line rather than a lesson in the style. - Make the trail a first-class output of the loop from day one, not a debugging afterthought. - Test against a chain deliberately longer than anything the previous design survived; that test is the only evidence the property holds end to end. - Version the step type explicitly, and treat a new variant as the breaking change it is for everyone who reads one. The defensible position is not `trampolining is better`. It is: this engine's chain length is decided by data we do not control, the failure it produces is abrupt and total, and the cheapest way to make the guarantee structural rather than advisory is to move the calling into one loop — for which we pay in style, diagnosability and a public type, and here is what we are building to cover each.
- Can the engine detect a handler that continued the chain with a direct call instead of returning a step?Not in general. The engine sees only the value it is handed, and a handler that recursed internally still returns a perfectly valid step at the end. Indirect signals exist — a step that takes far longer, or a failure whose path is unexpectedly deep — but the property is really upheld by review and by a test chain deeper than the old limit.
- What is the cheapest honest version of this change?Require the returning style only at the boundary whose length is unbounded, and leave bounded inner walks as ordinary recursion. Most of the guarantee is preserved, the imposition falls on one interface rather than all of them, and the boundary is documented and covered by a deliberately deep test.
- What does the team lose in diagnosability, and what replaces it?The failure path stops describing the chain: it shows the loop and the step that failed. The replacement is a trail the loop records as steps pass through it — which is strictly richer, since it can carry step identity and timing, but it is a thing you have to build and keep, not something the language gives you.
saying these in an interview costs you the question
- Thinks the engine can prevent a handler from recursing directly
- Says publishing the step type costs extending teams nothing
- Assumes the failure path will still show the logical chain
- Adopts the style for chains whose depth is bounded by design
- Treats adding a step variant as a private, internal change