Practice Worksheet · 3 min — Attempt the case yourself first. Read-only here — use the interactive version to save answers.
1. Interview prompt
Design a GenAI FDE (Forward Deployed Engineer) solution for a Reliable Workflow Orchestration System. One business process spans a document approval, a payment, an email and three internal systems, and any step can fail or sit waiting for days. Here, "exactly once" refers to the business outcome, not to individual messages.
2. Clarify the customer problem
- Which event tells us a workflow has truly finished?
- When a step fails, do we undo it or run a compensating action?
- How long can each step wait: minutes, hours or days?
- Who has authority to resume, rerun or override a single case?
- Will the payment provider accept an idempotency key?
3. Users and workflows
| User | Workflow | Current pain | AI assist opportunity | Human approval needed? |
|---|---|---|---|---|
| Workflow participant | ||||
| Operations team | ||||
| Application developer | ||||
| Auditor |
4. Requirements
Functional
Non-functional
- Latency target:
- Availability target:
- Cost budget:
- Security/privacy constraints:
- Audit/compliance requirement:
5. Data and integration map
| Data source | Format | Owner | Freshness | Permission model | Risk |
|---|---|---|---|---|---|
6. Proposed architecture
Fill in each part of your architecture for this case:
- Durable state and history:
- Scheduling:
- Timers and signals:
- Activity workers:
- Compensation:
- Operator repair:
7. Evaluation plan
| Metric | Good threshold | Bad threshold | Test dataset | Owner |
|---|---|---|---|---|
| Duplicate business effects | 0 | Even one repeat | Idempotency key logs | Reliability team |
| Replay determinism | Same decisions on every replay | One divergent decision | Replay jobs over stored history | Runtime engineering |
| Age of stuck workflows | Inside the expected wait | Past the agreed limit | Queue and state inspection | Operations |
| Manual repairs | Inside agreed tolerance | Over tolerance | Operator ticket records | Support operations |
8. Failure modes
- A worker dies just after the payment goes through
- The email sends but the CRM (customer relationship management) write fails
- An approval sits untouched for seven days
- A new definition ships while old instances still run
- An external API never returns
- Two workers try to move the same instance forward
9. Rollout plan
- Start with a single workflow; skip the platform.
- Make crash recovery the first gate.
- Show that no side effect happens twice.
- Give operators visibility early.
- Practise resume and compensation drills.
- Publish new versions; never edit live ones.
10. Weak vs strong answer
Weak: "I'd write an orchestrator that calls each API one after another and retries when something fails."
Strong: "I'd guarantee exactly once for the business effect. An append-only history writes the intent before each step runs. Receipts are stored by idempotency key. Waiting is a proper timer state. Every step has its own compensation. Each instance stays pinned to the definition version it started on."
11. Candidate scorecard
| Area | 1 | 3 | 5 | Score |
|---|---|---|---|---|
| Problem framing | "We need an orchestrator" | Lists the steps | Exactly once for the business outcome | |
| Architecture | Calls APIs in a chain | Keeps durable state | Timers, signals, per-step compensation | |
| Evaluation | "It finished" | Counts failures | Duplicates and replay determinism | |
| Production thinking | Builds a platform first | Pilots one process | Crash-recovery gate, operator repair | |
| Communication | Walks through the engine | Mostly clear | Opens with the double-charge case |
Model Answer · 12 min — Compare after attempting the worksheet: the ground a strong answer covers.
Work through the worksheet on your own first, then compare. This key models the questions, design, metrics, security controls and explanation a strong GenAI FDE (Forward Deployed Engineer) candidate brings when a demo has to become a production system.
Strong discovery questions
- Which outcome counts as success here: every technical step done, or the business effect landing exactly once? We would build differently for each.
- When something fails, do we roll it back or compensate for it? Each path is its own design.
- How much of this process runs without people? Which steps pause for minutes, which for hours, and which for days?
- After it starts, may a workflow branch, return to an earlier step or be cancelled part way?
- Which team owns each internal integration, and whose pager goes off when a workflow stalls?
- Who has the right to resume, rerun or override a stuck case?
- Will the payment provider honour idempotency keys? Without them, no retry can be called safe.
- Which audit evidence do we have to retain, and which actions require an approval log or a signature trail?
Strong functional requirements
- Core workflow: one process has to span payment, email, document approval and three internal systems, and its business effect must land exactly once.
- Persist state durably, with a history that is only ever appended to. A worker that dies then resumes from its last point rather than restarting.
- Model waiting as a proper state that timers and signals drive. While a person decides, no worker sleeps in a loop.
- Pair each step with an action that undoes its effect. Partial failure then leaves something repairable, never a silent half-success.
- Put a version on every workflow definition. An instance finishes on the version it began with.
- Operators need to resume, replay or compensate a single named instance, and each of those actions goes into the audit trail.
Strong non-functional requirements
- Latency: budget each step separately rather than the whole workflow. A person may take seven days to answer, yet every automatic step still has a bounded run time.
- Availability: we size for 10 million active workflows plus 100 million activities daily, which works out near 1,200 activities a second. Most of that pressure should land on the queue and the history store.
- Security: check identity and permissions at workflow start and on each signal after it. Resuming or repairing an instance is as powerful as starting one.
- Compliance: from the append-only history alone, we must be able to rebuild who approved what, at what time, on which definition version.
- Reliability: the exactly-once promise covers the business effect, not message delivery. Retries are safe only when backed by an idempotency key and a stored receipt.
- Cost: hold payloads outside workflow state. Inline documents would make the history store the largest cost we have.
Architecture explanation
- Deciding what happens next is the control plane's job. Its parts are the definition registry, the scheduler, the timer service, the signal gateway, the compensation engine and the operations UI.
- Doing the work is the data plane's job. Its queues and workers make the calls to payment, email, the CRM (customer relationship management) and the internal systems.
- Only the start call runs synchronously. It validates input, authenticates the caller, checks permissions, appends the first history event and hands back the workflow ID.
- All later work is asynchronous. That covers dispatching activities, waiting on approvals, waking on timers, retrying and compensating.
- Ownership splits cleanly. Workflow state, the step history and the intent to retry belong to the orchestrator. External systems own the rest: CRM records, the payment itself, email delivery and document approval.
- History only grows. Before a step runs, it records the intent; once the external system acknowledges, it records completion. Because of that order, replaying after a crash is safe.
- A deadline wakes an instance through the timer service, and a human action wakes it through the signal gateway. So a wait of seven days ties up no worker.
- Each instance row uses optimistic concurrency. Two workers holding stale state cannot both advance it.
Data model / integration assumptions
- Core records: WorkflowInstance carries id, state, definition_version and next_event. HistoryEvent carries instance_id, sequence, type and payload_ref. ActivityReceipt carries idempotency_key, external_ref and status. Timer carries instance_id and fire_at.
- Nobody edits a HistoryEvent once written. When a workflow crashes, replaying those events tells it what has already been done.
- An ActivityReceipt exists for every external side effect. A repeat of the same idempotency key gets the saved receipt back, and the card is not charged again.
- Every instance keeps the definition version it started on. Deploying mid-run therefore cannot silently swap the rules under it.
- Documents live in external storage, and payload_ref is just a pointer to them. That keeps state small and replay cheap.
Red-team risks
- Summary: a worker dying just after payment, a sent email beside a failed CRM write, days-long approvals, definitions changing under live runs, and a hung external API
- The charge succeeds, then the worker dies before writing the acknowledgement. Retry naively and the customer pays twice.
- Steps can half-work. Email says it sent, the CRM write times out, and the workflow moves on pretending all is well.
- An approval can sit for days. The right home for it is a waiting queue that reminds and escalates. It should not fire an alert or spin a polling loop.
- Definitions can change under running instances. Unless someone writes an explicit migration, older instances stay on the version they pinned.
- An external API can hang. Give every call a time limit and classify its errors as transient or permanent, so no retry runs forever.
Rollout plan
- Week 0-1: pick a single workflow; do not build a platform. Record what we assume about payment idempotency and how long waits may last.
- Week 1-2: gate one is determinism plus crash recovery. Kill a worker mid-run and demonstrate the instance resuming from its history.
- Week 2-3: prove that no crash, retry or replay produces a second external effect. Use the receipts and reconcile against downstream systems.
- Week 3-4: before scope grows, build the operator view. It has to show what is stuck, the reason, and the way out.
- Week 5: rehearse repairs on a stuck instance from real traffic. Resume it, replay it and compensate it, with audit on every action.
- Week 6-8: ship each change as a new workflow version and leave live versions untouched. A change is compatible, or it is a planned break that has been tested.
- After pilot: a second workflow comes on board only when stuck-workflow age, duplicate effects and the manual repair count are all within tolerance.
Evaluation plan
These thresholds are my own choices for this case, not industry standards. Expect to defend them, not just state them.
| Metric | What it proves | Strong threshold | Dataset / method |
|---|---|---|---|
| Duplicate effect count | The business outcome really happens once | A customer never gets a second charge or email | Reconcile idempotency logs against downstream systems |
| Replay determinism pass rate | After a crash, a workflow reaches the same decisions again | Each sampled history gives an identical replay | Replay jobs over stored history |
| Stuck workflow age | No instance hides in a non-final state | Every instance inside its expected wait | Inspecting durable state and the queues |
| Activity retry rate | Failures are short-lived blips, not a systemic fault | Flat; a sustained spike triggers an investigation | Telemetry from workers and the scheduler |
| Compensation rate | Partial failures get cleared rather than building up | Flat, with a reason for each workflow type | Workflow history |
| Manual repair count | Recovery does not depend on operators | Within the tolerance we agreed | Records of operator actions and tickets |
Weak answer
I'd build an orchestrator that hits email, payment and the internal APIs one after another and retries on any failure. This answer is weak because it is about wiring, not about what the customer gets. Nothing makes state durable, nothing makes the business effect exactly once, and nothing compensates. Retry after a charge has gone through and the customer is charged again.
Average answer
I'd pick a workflow engine that keeps state durably, add backoff retries to each step, and write an audit log. An approval would hold the workflow until a signal comes in. That is an improvement, yet gaps remain. Retry and compensation get blurred into one idea. Instances are not pinned to a definition version. And nothing explains how an operator can safely fix an instance that is genuinely stuck.
Strong answer
I'd open by restating the goal: long business processes where each business effect happens exactly once, state is visible, and a failed step has a compensation path. The thing at risk is a business transaction that lasts days, not messages in transit. That drives the design. History is durable and append-only, with intent logged before each step and completion logged after the acknowledgement. Retries stay safe thanks to idempotency keys and stored receipts. A wait is a timer or signal state. Every step has its own compensation. Each instance keeps its definition version, so deploying mid-run cannot rewrite its rules. Operators resume, replay or compensate, and all of it is audited. A second workflow waits until crash recovery works and nobody has ever been charged twice.
Interviewer scorecard
| Area | 1 - Weak | 3 - Average | 5 - Strong |
|---|---|---|---|
| Problem framing | "We need an orchestrator" | Names the steps, adds retries | Targets a business effect that happens once; separates the four stakeholder jobs |
| Requirements | "It should finish" | Wants durable state plus retries | Rollback or compensation, waiting as a true state, versioned definitions, repair rights |
| Architecture | API calls in a chain | A durable-state engine | Separate control and data planes; timer and signal services; a compensation engine; an operations UI |
| Data/integration | Mentions an audit log | Names instances and events | History that is only appended, receipts keyed on idempotency key, a pinned version, payload references |
| Evaluation | "It finished" | Counts failures | Stuck age, duplicate effects, replay determinism, plus compensation and repair rates |
| Safety/security | Not covered | Authenticates at start | Authorises every signal and every repair; the audit trail rebuilds approvals and controls |
| Rollout | Builds the platform | Pilots one process | A single workflow, a crash-recovery gate, operator visibility, new versions in place of edits |
| Communication | Describes the engine | Clear but generic | Starts from the double-charge case, names its assumptions, finishes on the first gate |
Final 2-minute spoken answer
I'd hold off on choosing an engine. We're asked to make payment, email, three internal systems and document approval work as one process, and any step in it may fail or stall for days. The tempting summary is "build an orchestrator that calls these APIs." That's only wiring. A better summary starts from the outcome. Long business processes must apply each business effect exactly once, show their state, and compensate when a step fails. Framed like that, the requirements fall out at once: idempotency, durable state, retries, pauses for human approval, an audit trail and compensating actions. The thing we're guarding is a single business transaction stretched over days, not messages crossing a network. For the architecture, the control plane has the definition registry, the scheduler, the timer service, the signal gateway, the compensation engine and the operations UI. The data plane is queues plus workers, and those workers call payment, the CRM and email. Only the start is synchronous. It authenticates, authorises, appends the first history event and returns the ID. The rest runs asynchronously. History only grows: intent goes in before a step, completion goes in after the acknowledgement. The key drill shows why that matters. Say a worker dies just after the payment succeeds. We don't charge again. We replay history, find the completed charge, and continue at the next step still open. Waiting seven days for approval is just a timer state; nothing polls. Each instance keeps its definition version, so a deployment can't alter its rules partway. My first move is one workflow. It goes live only after crash recovery works and duplicate effects are zero, and operators get visibility before we widen scope.
Tutorial 1 · 11 min — The full lesson: how to reason through this case from first question to rollout.
Table of Contents
- 1. The Customer Problem and Discovery
- 2. Clarifying Questions, Requirements, and Constraints
- 3. Scale Estimates, SLOs, and Capacity
- Start with the Load That Breaks the First Draft
- Working Assumptions and Activity Rate
- Separate Workflow State from Payloads
- Estimating Event-History Storage
- Estimating Task-Queue Throughput
- Sensitivity at 10x Growth
- Latency Budgets and Workflow SLOs
- Retry Math Is Not Compensation
- Which Estimate Should Drive the Architecture
- Communicating Uncertainty Like an Operator
- 4. Architecture and End-to-End Flow
- System Boundary and Customer Outcome
- Component Map: Control Plane vs Data Plane
- Top-Down Architecture and Trust Boundaries
- State Ownership and Consistency
- Sequence Diagram: Happy Path
- Failure-Path Overlay: Worker Crash After Payment
- End-to-End Flow, Step by Step
- Synchronous and Asynchronous Boundaries
- Queues, Backpressure, Caches, and Policy Placement
- Why This Decomposition Matters
- 5. Data Model, APIs, and Working Code
- 6. Security, Reliability, and Failure Handling
- The Uncomfortable Question That Anchors This Section
- Threat Model Before Failure Policy
- Failure Policies Are Design Decisions, Not Afterthoughts
- Failure Drill: Worker Crashes After Payment Succeeds
- Failure Drill: Email Succeeds but CRM Fails
- Failure Drill: Approval Waits Seven Days
- Failure Drill: Definition Changes While Instances Run
- Failure Drill: External API Times Out Indefinitely
- Evidence the Interviewers Want to Hear
- Interview-Sized Code Sketch: No-Double-Charge Invariant
- What to Say When the Interviewer Pushes on Trade-offs
- 7. Delivery Plan, Observability, and Business Impact
- Start with One Workflow, Not the Whole Platform
- Determinism and Crash Recovery as the First Gate
- Operator Visibility Before Broadening Scope
- Version Workflows Instead of Mutating Them
- Rollout Scorecard: Six Metric Layers
- Phased Rollout: Owners, Exit Criteria, Rollback Triggers
- Connect the Dashboard to the User Journey
- Core Product vs Adapter vs Configuration vs Shared Service
- Train Operators Before the First Wide Rollout
- The Risk Register Should Be Owned, Not Implied
- 8. Interview Walkthrough, Trade-Offs, and Practice
- Minute-Zero Opening: Lead with the Business Effect
- A Practical 50-Minute Answer Plan
- Keeping the Conversation at the Right Depth
- Trade-off Framing to Defend
- Follow-up Drills and Strong Answers
- Common Weak Answers and How to Repair Them
- Scoring Rubric for Self-Evaluation
- A 90-Second Architecture Summary
- Practice Loop: Solo, Pair, Implementation
- Coverage Notes
1. The Customer Problem and Discovery
From Feature Request to Customer Outcome
- Here is the ask: a single process has to drive a document sign-off, a payment, an email and updates in three in-house systems. Any one of those steps may break, or may sit idle for days.
- The people around the table want different things from day one:
- The business sponsor simply wants every case to reach the end.
- The operations lead asks for retries, a clear view of each case and a way to roll back.
- The application team does not want yet another fragile web of direct integrations.
- The auditor needs proof of each approval: who gave it, at what time and under which control.
- Resist naming a tool first. Put the problem into a single sentence that does not yet pick a product.
- A weak, feature-led version: "Let's build an orchestrator that calls the payment, email and three internal APIs". It is accurate, yet it puts the wiring ahead of the result the customer actually wants.
- A strong, outcome-led version: "apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails."
- That one line already demands idempotency, durable state, retries, pauses for human approval, auditability and compensating actions.
- It also exposes what the job really is. We guard a business transaction spread over days, not a stream of messages.
- Framing it like this shows the habit panels hire FDEs (Forward Deployed Engineers) for: you take vague customer words and turn them into a goal you can measure.
Stakeholder Map and Jobs-to-Be-Done
- Group the four stakeholder sets by the job each needs done, not by title. Lump them together and you will optimise the wrong part of the system.
- Workflow participants — they need to "look at a case and decide, with the full context in front of them."
- Operations teams — they need to "spot stuck cases, learn the cause and bring each one back safely."
- Application developers — they need to "connect a system once, without writing bespoke retry code for every corner case."
- Auditors — they need to "rebuild the trail of decisions and check that each control held."
- Reliability means something different to each. Participants want clarity. Operators want to recover cases without crossing integration boundaries. Auditors want a trail they can follow.
- A strong answer meets every group's need, and no single group gets to steer the whole design.
The Two-Minute Opening Answer
- An opening you could say out loud in the room:
- "So we have a document sign-off, a payment, an email and three in-house systems to coordinate, and each step might fail or stall for days. First I'd pin down whose workflow this is, which business event counts as done, and which steps need a person to approve and which run on their own. My working goal: apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails. Unless you say otherwise, I'll take durable state, retries, an audit history and recovery from worker crashes as given. Next I'd size the volume, the latency and how much failure we can absorb. Then I'd build the orchestration, security and observability on top of those numbers."
- In under two minutes that opening repeats the ask, names who it serves, sets the goal and flags each assumption where facts are missing. Under time pressure this is how you compress: ask fewer questions, and make each one count.
Highest-Leverage Discovery Questions
- You do not need equal depth on every subsystem. Pick the questions that remove the most doubt per minute.
- Clarify the business finish line:
- Which event tells us the workflow is done?
- Do we count success when every technical step finishes, or when the business effect lands exactly once?
- For each kind of failure, do we roll back or compensate?
- Identify the workflow shape:
- Does it run with no humans, with a person in the loop, or a blend?
- Which steps may pause for minutes, which for hours and which for days?
- Can a case fork, loop back to an earlier step or get cancelled halfway?
- Determine blast radius and ownership:
- Which team owns each internal integration?
- Whose pager goes off when a case stalls?
- Who has the authority to resume, rerun or override a case?
- Pin down trust and compliance requirements:
- Which evidence must we keep for audit?
- Which actions need an approval log or a signature trail?
- Do residency, retention or data-minimisation rules apply?
- When the interviewer holds facts back, say that openly and record the gap as an assumption. For example: "I can't tell yet if payment is the final business effect or only one link in a longer approval chain. So I'll treat a payment retry as safe only when the provider honours idempotency keys and a durable workflow record proves the effect landed once."
The Assumption Ledger
- Think of the assumption ledger as a running list of things you treat as true until the customer says otherwise. It is a working tool, not paperwork.
- Every entry worth writing has four fields:
- Assumption: the belief you are acting on for now
- Impact: how the design changes if it holds or fails
- Verification: the step that would confirm it
- Owner or follow-up: the person who can answer, or the deadline for an answer
- First example:
- Assumption: the payment provider accepts idempotency keys
- Impact: decides if a retry can run without charging twice
- Verification: check the provider docs or ask whoever owns payments
- Owner or follow-up: the payment integration team, before the design is final
- Second example:
- Assumption: participants can live with a pause of several days
- Impact: shapes how long state is kept, when cases expire and how people get notified
- Verification: check with the business sponsor and the support team
- Owner or follow-up: the product owner, ahead of any sizing work
- Every discovery blends confirmed facts with provisional guesses. A guess nobody writes down quietly turns into a design decision. In an interview, splitting checked requirements from assumptions often earns more credit than naming a particular product.
🎯 Interview Pointer: Panels like to test whether your discovery stays honest when the clock is running. Keep the four-field ledger (assumption, impact, verification, owner) ready to use. It shows rigour and costs you almost no time.
Turning Discovery into Scope, Risks, and Success
- Discovery is where the system gets its shape, so treat it as real design work. When it ends, you should hold four outputs, in your head or on the whiteboard:
- Scope: the workflows we will handle and the ones we will not
- Assumptions: the beliefs we act on until someone confirms them
- Risks: the places where duplicates, lost data or unauthorised actions could creep in
- Owners: the named owner of every integration, approval, escalation and support route
- Describe success the way the business would, not the way infrastructure would. Skip "messages processed" and "tasks retried". Measure the count of completed workflows. Add the duplicate-prevention rate, the compensation success rate and the mean time to detect stuck cases. Also check that an operator can walk through any case from its first step to its last.
- A success test small enough for an interview: the business effect landed once, the user sees where the case stands, and each exception leads to a known compensation or escalation.
Stakeholder Map in Practice
- Take a concrete case. A contract gets approved, a payment fires, an email goes out and three internal systems get updated.
- The end user is a real person waiting for an approval or a payout. It is not "the API".
- The operator is whoever is on call when a worker dies just after the payment went through.
- The security owner rules on who may resume or override a stuck case.
- The executive sponsor wants a process that behaves predictably, passes compliance and never has to be pieced back together from spreadsheets.
- Again, the aim is to serve all four groups while none of them takes over the design.
The Interview Signal Hiring Teams Listen For
- Panels check more than your diagrams. They watch whether you can go from a customer's pain to real technical leverage without sliding into vague platform talk.
- The best candidates make the problem specific. They name the people, state their assumptions out loud and describe success in words the customer would use.
- Draw the architecture only once you can say whose workflow is changing and how success gets measured. Before that point, you are listing software features, not designing a system the business can rely on.
Tutorial 2 · 7 min — The full lesson: how to reason through this case from first question to rollout.
2. Clarifying Questions, Requirements, and Constraints
Choosing Which Unknowns to Pay For
- Skip the boxes and arrows at first. Start by picking which open questions deserve scarce interview time.
- Expect the interviewer to answer only some of what you ask. Strong candidates live with some ambiguity and instead:
- Find the handful of choices that would reshape the design if they went the other way.
- Guard the riskiest constraint first: no business effect may happen twice when a step fails, pauses, or comes back in the wrong order.
The Question Tree for Discovery
- Keep discovery questions few and pointed, each tied to a concrete design choice:
- In what order do the steps run, and can any of them branch or be skipped? → The answer tells you if a simple sequential runner will do, or if you need a real state machine.
- Which steps can be undone after they succeed, and which cannot? → This marks where compensation is safe and where retries need extra caution.
- What is the longest a workflow may stay open before it times out, gets archived or is cancelled? → Storage and timers follow from this; week-long waits rule out in-memory state and short-lived queues.
- At what point does a person approve, and how long may that approval sit? → This drives how you model paused states, reminders, escalations and cases nobody comes back to.
- Do the downstream systems accept idempotency keys, block replays, or take a unique business reference? → If yes, retries are safe; if not, the orchestrator has to wrap each call in its own deduplication layer.
- If a definition changes mid-flight, how do older running instances stay compatible? → This bites the moment someone wants a new approval step while 10,000 old workflows are live.
- Partial answers are no reason to stall. State your assumptions out loud and tie each one to the risk it covers.
- The safest anchor here: payment is the riskiest step and cannot be reversed. Whatever else degrades, the design must still block a second payment.
From Discovery to Requirements
- Turn what you learned into a ranked list, not a wish list, sorted into must, should and could:
- Must: durable state machine, persisted transition history, idempotent activity execution, retry and timeout policies, human signals and timers, saga compensation, operator recovery.
- Should: versioned workflow definitions, so running instances can evolve without breaking.
- Could: deeper analytics, bespoke dashboards, or branching rules users configure themselves, as long as none of them puts correctness at risk.
- The reason behind each must-have:
- Durable state machine — the workflow outlives a crashed process and waits out long pauses.
- Persisted transition history — operators can see what happened, and a restarted worker recovers its intent from the record instead of guessing.
- Idempotent activity execution — retries will happen. A retry with no deduplication is just a duplicate side effect under a nicer name.
- Retry and timeout policies — outside systems break, slow down, and sometimes stay silent forever.
- Human signals and timers — an approval usually arrives as a business event, not as an API callback.
- Saga compensation and operator recovery — some steps can be reversed and some cannot, and the design has to show which is which.
Functional vs Nonfunctional Requirements
- Functional: each case goes from approval to payment, then on to notifications and internal record updates.
- Nonfunctional: it keeps going through process crashes and even a lost region, keeps a history people can inspect, never pays twice, and changes without breaking workflows already under way.
- Keep the two lists apart. Mixed, they hide trade-offs; split, they show what the system does and how safely it must do it.
- Each nonfunctional requirement, restated as an outcome you can measure:
- "Survive process and region failures" = after losing a worker or a whole site, the workflow picks up again with no committed state lost.
- "No duplicate payment" = a business key, durable deduplication or the provider's idempotency mechanism guards the payment, and the same guard is used every time.
- "Inspectable history" = transitions, retries, approval signals, timeouts and compensation events all land in durable, queryable storage.
- "Compatible evolution of running instances" = changing a workflow definition leaves no older execution stranded, and no running state machine gets reinterpreted halfway through.
Constraints vs Preferences
- Constraints shrink the set of safe designs. Preferences make the system nicer to use, but they can give way.
- For this prompt:
- Irreversible payment handling — a constraint.
- Human approval latency — a constraint if the business really waits days, a preference if approvals usually land within hours.
- Workflow versioning — a constraint as soon as the customer confirms that in-flight cases will outlive a deployment.
- A polished dashboard — a preference.
- Saying the label aloud for each item shows you can keep scope creep out of the architecture.
🎯 Interview Pointer: If the interviewer adds a "nice to have" partway through, label it as a constraint or a preference before you respond to it. Few habits signal architectural discipline under pressure as clearly.
What the MVP Will Not Support
- Stating non-goals up front stops the solution from spreading:
- No branching logic written freely by users, no multi-tenant visual rule builders, and no promise of exactly-once delivery to every downstream system.
- No effort to make every action reversible. In general, payment and some external writes cannot be safely undone.
- No keeping transition history forever. A clear policy sets how long it is retained.
- No one compensation strategy that fits every step. Some failures will need a person to recover them.
- Trust in an orchestration system comes from handling a few hard problems well. Posing as a complete business-process platform on day one does not earn it.
Requirement-to-Component Traceability
- A short traceability table links each requirement to the parts that deliver it. Later it links those parts to implementation and operations:
| Requirement | Primary component(s) |
|---|---|
| Durable state machine | Workflow engine, persisted workflow store |
| Persisted transition history | Event/history store, audit log |
| Idempotent activity execution | Activity executor, deduplication keys, provider idempotency adapter |
| Retry and timeout policies | Scheduler, timer service, retry policy engine |
| Human signals and timers | Signal intake, approval queue, deadline manager |
| Saga compensation and operator recovery | Compensation handlers, admin console, manual override path |
| Survive process and region failures | Replicated storage, replayable execution model, failover procedures |
| No duplicate payment | Payment guardrail, unique business reference, payment ledger check |
| Inspectable history | Query API, history viewer, event export |
| Compatible evolution | Versioned workflow definitions, migration rules, compatibility gates |
The Assumption Discipline an FDE Needs
- A strong answer admits what it does not know: "My assumptions are that payment can't be reversed, approvals can sit for days, and definitions will change while old instances are still live. Given that, I'll optimise for durable state, a clear history, idempotent side effects and version compatibility."
- Strong FDEs (Forward Deployed Engineers) discover, prioritise and keep delivery disciplined all at once. From patchy customer input they build a system that is safe to ship, small enough to reason about, and open to growth.
- What comes next: the requirements become scale estimates and SLOs (service level objectives), and the architecture begins to firm up.
Tutorial 3 · 10 min — The full lesson: how to reason through this case from first question to rollout.
3. Scale Estimates, SLOs, and Capacity
Start with the Load That Breaks the First Draft
- Most candidates open with a sensible sketch: a workflow service, a queue, a pool of workers, a state database and a blob store for attachments. At average load, it holds up.
- Its weak spot is timing, not just volume. Long waits, retries and deadline pressure stack up, and that mix is the limit the sketch hides.
- Picture a workflow that idles for days. Then approvals arrive, a payment gateway answers, or a downstream system recovers, and the work spikes at once. A system sized for the mean will miss business deadlines once it is live.
- So estimate the envelope next. Count the workflows running at the same time and the events they emit. Work out how much state needs durable retention, how fast tasks must go out, and how much slack peaks and partial outages call for.
Working Assumptions and Activity Rate
- Treat the scenario figures as planning inputs, not fixed truth:
- 10 million active workflows
- 100 million activities per day
- Week-long waits between some steps
- Those figures already reveal the shape: a durable state machine. Most of its population sits idle, while a smaller slice keeps moving.
- Turn the daily figure into a rate: 100,000,000 activities/day ÷ 86,400 seconds/day ≈ 1,157 activities/second on average.
- The mean is a useful anchor, but you do not provision for it. Real peaks can land several times higher. Work bunches up in office hours. Reminders go out in a morning batch. A dependency outage holds retries back, then releases them together.
- Say the framing out loud. These multipliers are my own planning choices for this case, and I'd defend them; they are not industry standards:
- Average dispatch rate: roughly 1.2k activities/sec
- Peak dispatch rate: 3x–10x average, set by how the customer actually uses the system
- Headroom: for a new system, at least double the observed peak; go higher still if regional failover or retries matter
- Growth factor: run the numbers at both 2x and 10x future scale before you fix partition counts or storage layout
Separate Workflow State from Payloads
- A frequent scaling error is to push everything into the workflow history.
- Keep durable state small. It needs only the facts that let you replay, resume, audit and compensate: version markers, step status, timestamps, business references and correlation IDs.
- Big items go elsewhere. Documents, PDFs, images and payloads from internal systems live in object storage or a document store, and the workflow keeps a pointer.
- The split pays off in three ways:
- Replay cost — if every retry or rehydration drags megabytes of payload into the orchestration engine, latency climbs and worker memory comes under strain.
- Retention cost — audit and support often need workflow histories kept for a long time. Park large payloads on the hot path and the operational ledger becomes a costly archive.
- Blast radius — when every event record carries its own copy of the payload, a leak or an accidental change does far more harm.
- A useful rule: workflow state holds just enough to keep the system deterministic and inspectable. Payload storage takes the data that is large, mutable or sourced from outside. The workflow points at the payload; it never turns into it.
Estimating Event-History Storage
- The method is a quick product, not a magic number: active workflows × events retained per workflow × bytes per event.
- Inputs (10 million is the scenario's figure; the per-workflow numbers are my own):
- 10 million active workflows
- An average of 40 retained events per workflow
- 1 KB per event record (an illustrative size)
- The result is about 400 GB of raw event data. That leaves out replication, indexing, compaction overhead and backups.
- If each event takes 2 KB rather than 1 KB, the total grows to about 800 GB.
- If the true active set is 20 million, not 10 million, the figure doubles once more.
- The estimate does not prove a final figure. Its job is to show which dimension drives the storage design.
- When history is kept for a long time, give the colder audit path its own home, apart from the hot execution path that holds task leases and execution metadata. Compaction, tiering and retention policies then become simpler to think through.
Estimating Task-Queue Throughput
- The task queue is where operations choke. Every status update, payment request, internal API call and approval notification ends up as a queued task.
- At 100M activities/day, the system has to accept, lease and complete a sustained flow above ~1,157 activities/second.
- That figure still hides load. Each activity usually triggers at least two queue actions, an enqueue and a dequeue. Leasing, visibility timeouts and requeues can add more.
- So the throughput envelope covers both creating tasks and running them:
- Task enqueues: grow roughly in line with activity count
- Task leases and acknowledgements: one or more per activity, often extra once retries kick in
- Retry traffic: added load after transient failures or worker restarts
- The queue subsystem must survive a retry storm and still serve new tasks. A practical design splits queues by priority class, workflow type or workflow tenant. Then no single customer, and no runaway workflow family, can monopolise dispatch capacity.
Sensitivity at 10x Growth
- A sensitivity table makes the scaling decision concrete. It shows what gives way first when the assumptions shift:
| Scenario | Active workflows | Activities/day | Avg activities/sec | Likely architectural pressure |
|---|---|---|---|---|
| Base case | 10M | 100M | ~1.2k | Durable history, a moderate degree of partitioning, a worker pool kept warm |
| 10x growth | 100M | 1B | ~11.6k | More aggressive sharding, tighter isolation between queues, heavier compaction and tiering |
- At 10x, I would not expect application-server CPU to give out first. I'd expect replay latency, hot spots in the queue, storage fan-out or operating cost to hit the wall sooner.
- If the design would need a full rewrite to take that growth, say so. Then pick a layout that partitions more easily from day one.
Latency Budgets and Workflow SLOs
- Workflow orchestration needs a different set of SLIs/SLOs from a typical API server:
- Availability SLI (service level indicator): the share of time that workflow submission, progress updates and operator controls can be reached
- Latency SLI: how long it takes to accept a new step, send out an activity, and show a state change in the UI
- Freshness SLI: the lag between a real-world event and its appearance in workflow state
- Quality SLI: the rate of stuck workflows, failed compensations, missed transitions or duplicate side effects
- Security SLI: the fraction of requests that pass authorisation correctly and get audited; the rate of sensitive-data exposure is pushed toward zero by design, never claimed to be impossible
- Cost SLI: cost per workflow or per thousand activities, plus the support effort that operator intervention demands
- Turn these into objectives the customer can feel. Few customers care whether a task lease takes 80ms or 120ms. They do care when a payment step shows up in the UI so late that an operator thinks it was lost and retries it by hand. They also care when a workflow that ought to be compensating stays hidden for hours.
- Size the queue subsystem to ride out a retry storm with new tasks still flowing. Partitioning by tenant, workflow type or priority class stops one runaway workflow family from monopolising dispatch capacity.
Retry Math Is Not Compensation
- This formula puts a bound on repeat attempts and avoids a thundering herd:
backoff_n = min(cap, base × 2^n) + jitter
- Here n is the retry attempt number, base is the first delay, cap is the longest allowed delay, and jitter is randomised variation so retries do not fire in sync.
- Exponential backoff spaces out attempts after a failure. That shields dependencies and gives a transient incident room to clear.
- Backoff is not business compensation:
- The payment goes through but the email fails. Retrying the email is the right move.
- The payment goes through, and later the workflow finds a validation error. Backoff cannot reverse the charge. You need an explicit compensation step, such as a refund or marking the case for review.
- Retry says "try once more, later". Compensation asks "which business action reverses or limits a side effect that already happened?" Keep the two apart.
- Treat cap and jitter as operational trade-offs tied to the business process, never as habits:
- A low cap recovers user-facing work faster, but it can swamp a flaky dependency.
- A high cap protects the dependency, but customers wait longer to see progress.
🎯 Interview Pointer: Interviewers like to set the "retry vs. compensation" trap. Say plainly that backoff never stands in for a business-level reversal, such as a refund, when a side effect that already completed has to be undone.
Which Estimate Should Drive the Architecture
- Total workflow count alone rarely decides it. The mix of long waits, visible state and safe side effects does:
- Week-long waits demand durable storage and the ability to replay.
- 100 million daily activities demand a scalable queue and a partition strategy.
- Side effects you cannot undo demand compensation tooling, unique business keys and idempotency.
- Let the stress point choose the components, not whatever is easiest to sketch:
- When history volume is the main load → tune storage tiering and retention policies.
- When the dispatch rate leads → invest in queue partitioning and worker elasticity.
- When external APIs are the pressure → work on retries, circuit breaking and throttles per integration.
- When manual recovery takes the most effort → improve observability, the operator UI and audit trails.
Communicating Uncertainty Like an Operator
- Interviewers look for proof that you steer with estimates, rather than pretend to be certain.
- A sample answer: "I'd take 10 million active workflows as the starting working set. But I'd plan partitions and queue namespaces for a 10x sensitivity case, since growth and retry bursts rarely spread load evenly. If the customer later says only 20% of workflows run long, storage gets simpler. If they say 80% have week-long waits, retention and the replay path jump to the top of my list."
- That answer shows you picking the smallest design that stays safe under realistic growth and failure. You are not over-building for every future you can dream up.
- This topic sorts candidates in the job market. Plenty of engineers can talk about distributed systems in the abstract. Fewer can name the number that matters, explain why, and say which decision it moves.
- Treat estimates as tools for decisions. Each number should back an architectural choice or an operational limit. If a metric leaves partitioning, storage, retry policy, latency budget and support model all unchanged, it probably does not belong at the front of your answer.
Tutorial 4 · 10 min — The full lesson: how to reason through this case from first question to rollout.
4. Architecture and End-to-End Flow
System Boundary and Customer Outcome
- Start with what the customer needs and design back from it. The outcome: apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails.
- Retries still happen. "Exactly once" means nobody is charged twice or sent the same message twice, no step goes missing unnoticed, and you can show all of that.
Component Map: Control Plane vs Data Plane
- Draw a line between the control plane and the data plane before anything else. Without it, the parts never fall into place.
- Components:
- Workflow definition registry — holds the approved workflow templates. It is the source of truth for their step order, their retry and timeout rules, and which compensation undoes which step.
- Durable history store — the system of record. It logs every attempt, signal, state transition, receipt and final outcome.
- Workflow scheduler — derives the next runnable transition from durable history and hands it off to run.
- Activity task queues — buffer the handoff from scheduler decisions to the workers.
- Workers — make external calls, apply internal side effects and run local validation.
- Timer service — brings a workflow back after a delay, at a deadline, or when a human wait ends.
- Signal/approval gateway — takes explicit signals from people or systems and releases the workflow waiting on them.
- Compensation engine — after a terminal failure, runs business actions that undo, as far as they can, the steps already finished.
- Operations UI — shows live state, who is blocking whom, and the retry history, with pause, resume and override controls.
- Control plane = the scheduler, the registry, the timer service, the signal gateway, the operations UI and the compensation engine. They decide what happens next.
- Data plane = the queues and the workers. They do the work.
- Everything rests on this split. It lets decision-making scale apart from side-effect execution.
Top-Down Architecture and Trust Boundaries
- Top-down flow:
- A customer or internal caller sends an HTTP/API request carrying an idempotency key.
- The Workflow API receives it.
- The API looks up the permitted workflow template and policy in the Workflow Definition Registry.
- It writes the opening event into the Durable History Store.
- It may also tell the Workflow Scheduler that a fresh instance can run.
- The Workflow Scheduler reads durable history and pushes runnable work onto the Activity Task Queues.
- Workers pull from the queue and call Internal Systems A/B/C, the External Payment System and the Approval/Document Service.
- Workers write their results and receipts into the Durable History Store.
- The Timer Service tracks deadlines and wakes workflows that must carry on later.
- The Signal/Approval Gateway passes human approvals, rejections and outside-system signals on to the scheduler.
- When finished work must be unwound, the Compensation Engine sends business compensations out through the queues.
- The Operations UI reads the store and scheduler state, so operators can watch, pause, override or resume a workflow without risk.
- Three trust boundaries exist. Each needs its own authentication, authorisation and logging policy; do not merge them into one generic "service layer":
- Public/customer-facing API — takes the start request and its idempotency key, but cannot count on the caller retrying with care.
- Worker edge — the outside systems here may fail, time out or only partly finish.
- Human approval path — slow, error-prone, and every decision on it must be auditable.
State Ownership and Consistency
- Only the durable history store owns the canonical progress of a workflow.
- Queues handle delivery. The truth does not live in them.
- Workers suggest effects. They hold no truth either.
- The scheduler does not rely on what it keeps in memory. It rebuilds its view from durable history each time.
- That gives the central consistency rule: once an event is accepted, recovery runs off the history store's record of it.
Sequence Diagram: Happy Path
- The happy path in order. Time runs from top to bottom; the actors sit left to right:
- Caller → Workflow API: start a workflow, passing an idempotency key.
- Workflow API → Durable History Store: record the scheduled transition.
- Workflow API → Workflow Scheduler: flag the fresh instance as runnable.
- Workflow Scheduler → Activity Task Queues: send out the activity and log the attempt.
- Activity Task Queues → Worker: hand over the activity.
- Worker → External System(s): carry out the side effect, such as a payment, an approval lookup, an email or an internal change.
- Worker → Durable History Store: write the completion receipt.
- Workflow Scheduler → Signal Gateway or Timer Service: pause until a timer fires or a human signals.
- Signal Gateway or Timer Service → Workflow Scheduler: wake the workflow or let it go on.
- Workflow Scheduler → Activity Task Queues: if policy permits, try a transient failure again.
- Workflow Scheduler → Compensation Engine: on terminal failure, compensate the steps already done.
- Workflow Scheduler → Durable History Store: close the workflow and write an audit summary.
- Walk through every step in order and skip no handoff. The interviewer should hear control leave the model, reach deterministic services, and return.
Failure-Path Overlay: Worker Crash After Payment
- Rehearse this failure above all others: the payment goes through, and then the worker crashes.
- Event sequence:
- The caller starts the workflow and supplies an idempotency key.
- The API records the scheduled transition in history.
- The scheduler sends out the payment activity and logs an attempt.
- The activity queue hands that attempt to a worker.
- The worker sends the charge to the payment system.
- The payment system reports success.
- The worker dies before the completion receipt is written.
- The scheduler notices that no completion arrived, or that a timeout expired.
- It then picks a path: redeliver the activity, or reconcile from the state already on record.
- Transient or recoverable → the queue redelivers the activity and the worker writes the completion receipt.
- Terminal once some steps are done → the compensation engine starts compensation for the finished steps and routes the compensating actions through the queue.
- Last, the history store closes the workflow and writes an audit summary.
- The charge may have gone through before the worker died. So "worker lost" must never be read as "business action lost".
- Only the record prevents a double charge on retry: the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does. The scheduler reads the history to see what already happened. Depending on that, it safely reissues the step or hands the case to compensation.
🎯 Interview Pointer: This is the follow-up to expect most: the payment succeeds, then the worker dies before the receipt is written. Know the answer cold: the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does.
End-to-End Flow, Step by Step
- Give the happy path as numbered steps, not as a loose story:
- An idempotency key comes with the start request, so a retry cannot spawn a second business process.
- The API checks the request, then adds a scheduled transition to durable history.
- The scheduler picks up that transition, puts an activity on the queue and logs the attempt.
- A worker takes the activity, carries out the side effect and writes a completion receipt to durable history.
- The workflow then enters a wait state until a timer fires or a human sends a signal.
- A transient failure gets a retry under policy, not an instant escalation.
- If the workflow fails terminally after some steps are done, the compensation engine runs compensating actions for those steps.
- The workflow ends with an audit summary: the route it took, its waits and retries, any manual interventions, and the final effect.
- Read the flow as a series of consistency points. The request is durable before anything downstream changes. The side effect lands before the workflow reports success. The audit summary comes last, because it is built from history rather than from worker memory.
Synchronous and Asynchronous Boundaries
- Synchronous: validating the start request, authentication, permission checks, writing the first history event, handing back the workflow ID, and most reads from the operations UI.
- Asynchronous: activity dispatch, approval waits, timer wakeups, retries, compensation, and most calls to downstream integrations.
- The boundary does real work. It keeps the API quick to answer while the business process behind it waits for days.
- Make everything synchronous → a fragile chain of requests that snaps as soon as a human is slow.
- Make everything asynchronous without a solid history model → nobody can explain what happened, and operators lose control.
Queues, Backpressure, Caches, and Policy Placement
- Queues sit at the seam between scheduling and execution; they do not belong in the state store. There they soak up bursts of demand and stop slow workers from holding up the rest.
- Apply backpressure in the scheduler and on the queue admission path. If one tenant, partition or dependency backs up, throttle new dispatches so retries cannot swamp the fleet.
- Use caches only for data that is read often and carries little risk, such as workflow definitions and UI summaries. A cache must never be the sole home of execution state.
- Enforce each policy near the point where its decision is made: retry limits in the scheduler, approval rules in the signal gateway, and permission checks ahead of any compensation or manual override.
- How history and queue load spread out depends on the partitioning key: tenant ID, workflow ID, or a customer/process key. The aim is fewer hot spots, plus causal order wherever the business needs it; sharding for raw scale alone misses the point. When two steps for one process instance must never race, choose a key that puts them in the same partition.
Why This Decomposition Matters
- A product owner thinks in invoices and approvals. An engineering team thinks in retries, locks and queue depth. Both must be able to follow this one architecture, and being able to speak to both is a strong signal in the job market.
- Use the diagram to tell four stories: data, identity, state and failure.
- Data moves from the caller to history, on to the scheduler and the worker, and back.
- Identity is carried by auth, by idempotency keys and by approval context.
- State is kept in durable history.
- Failure is met with retries, timers and compensation, not with optimism.
Tutorial 5 · 9 min — The full lesson: how to reason through this case from first question to rollout.
5. Data Model, APIs, and Working Code
The Smallest Safe Slice of the System
- Boxes on a whiteboard don't convince an interviewer. Durable records, strict contracts and one code path that runs the riskiest part safely do.
- Here the riskiest part is the state machine. It picks the next move once a step has succeeded, failed or been replayed. The approval screen and email sender matter far less.
- Get the state machine wrong and the customer suffers, however healthy the rest is: cards charged twice, lost approvals, workflows that look frozen for good.
Core Durable Records
- Four records carry the design. Give each a purpose, a key, a lifecycle and a retention rule:
WorkflowInstance(id, definition_version, state, next_event)— the single row that owns one business process.- Purpose: the one durable record of where the workflow stands and where it will resume.
- Primary key:
id. - Lifecycle: created at start, changed on each transition, and finally archived or kept as the customer's policy requires.
- Retention: hold the live row while the workflow runs. Once it finishes, keep a slimmer historical copy if audit or replay needs one.
HistoryEvent(instance_id, sequence, type, payload_ref)— an audit trail you only ever add to.- Purpose: rebuild the exact order of events without leaning on memory or short-lived logs.
- Primary key:
(instance_id, sequence). - Lifecycle: new events get appended; past events are never edited.
- Retention: keep events while debugging, customer audits and replay still need them. After that, policy moves them to cold storage or deletes them.
ActivityReceipt(idempotency_key, external_ref, status)— a note of each side effect that has been tried or has finished.- Purpose: stop a retry from taking payment again, sending a second email or repeating an update.
- Primary key:
idempotency_key. - Lifecycle: written just before or just after the external effect, depending on how the dependency behaves.
- Retention: keep it for as long as duplicates must be blocked.
Timer(instance_id, fire_at)— a record of each scheduled wake-up.- Purpose: wake a workflow after a long wait, expire an approval, or check whether an outside job has finished.
- Primary key:
(instance_id, fire_at), or whatever key the scheduler uses instead. - Lifecycle: insert it when a wait starts and delete it once the wait ends. Insert it again if the workflow is retried.
- Ownership is split. The orchestrator holds workflow state, the history of steps and the intent to retry. Payments, document approval, email delivery and CRM (customer relationship management) updates belong to external systems. The orchestrator may record a payment, but it never acts as the financial ledger.
API Contracts
- Four API contracts expose the state machine. Each is quick to explain, yet strict enough that no client can slip vague work into the engine:
POST /v1/workflows/{type}— begins a new workflow.- Authentication: normal caller authentication, scoped to the tenant or customer.
- Idempotency: the caller sends an idempotency key, so a retried call never yields a second instance.
- Request: the workflow type, the business payload, caller context and, if wanted, correlation metadata.
- Response: the instance id, the starting state and a status reference that does not change.
- Errors:
400for a malformed request;401/403when auth fails;409while a request with that key is still in flight;422when the key returns with a different payload, or the inputs make no business sense. This split of 409 and 422 comes from the IETF Idempotency-Key draft, which had expired by Sep 2026: an Internet-Draft, not an RFC.
POST /v1/workflows/{id}/signals— accepts input from a person or another system.- Authentication: the caller needs permission to signal this workflow or tenant.
- Idempotency: a signal id combined with a payload hash, or a caller key, filters out double-clicked approvals and repeated system callbacks.
- Response: the accepted signal and the transition it caused, or a no-op when that signal has already been applied.
GET /v1/workflows/{id}/history— returns the audit trail.- Authentication: the caller needs read rights for the workflow's tenant and policy scope.
- Semantics: it never writes; results come back in pages, sorted by sequence.
- Errors:
404when the workflow does not exist;403when access policy hides it.
POST /v1/workflows/{id}/repair— gives operators, or automation they have approved, a way to resume, replay or compensate.- Authentication: elevated operator authorisation with a narrow scope.
- Idempotency: each repair action carries its own idempotency token. An operator who clicks twice should not cause twice the side effects.
- Semantics: the call must state plainly which action it takes: retry a step, compensate a finished step, or resume from a known checkpoint.
- Duplicates should behave predictably. If the payment step sees one idempotency key twice, the first call takes the money and saves a receipt. The second returns that receipt, or a "completed already" response.
The Implementation Slice That Proves the Design
- One saga step is enough to show the key behaviour: wait for approval, take payment idempotently, update the CRM, and compensate if a downstream step fails for good.
- The sketch is narrow by design. Production code would also need a scheduler, queue workers, persistence, serialisation, distributed locking and stronger typing. Here we keep only the state transitions an interviewer cares about most.
from dataclasses import dataclass
from typing import Protocol
class PermanentError(Exception):
pass
@dataclass(frozen=True)
class Order:
id: str
amount_cents: int
customer_email: str
@dataclass(frozen=True)
class PaymentReceipt:
idempotency_key: str
external_ref: str
status: str
class Activities(Protocol):
async def charge(self, order: Order, idempotency_key: str) -> PaymentReceipt: ...
async def update_crm(self, order: Order, idempotency_key: str) -> None: ...
async def send_receipt(self, order: Order, idempotency_key: str) -> None: ...
async def refund(self, payment: PaymentReceipt, idempotency_key: str) -> None: ...
async def order_workflow(order: Order, activities: Activities, wait_for_approval):
if order.amount_cents <= 0:
raise ValueError("order amount must be positive")
approved = await wait_for_approval(order.id, timeout_days=7)
if not approved:
return "rejected"
payment_key = f"pay:{order.id}"
payment = await activities.charge(order, idempotency_key=payment_key)
try:
await activities.update_crm(order, idempotency_key=f"crm:{order.id}")
await activities.send_receipt(order, idempotency_key=f"mail:{order.id}")
except PermanentError:
await activities.refund(payment, idempotency_key=f"refund:{order.id}")
raise
return "completed"
- What each part does, in an order you can talk through out loud:
OrderandPaymentReceiptare typed objects at the boundary. They stop the orchestration code from passing loose blobs around.PermanentErrorseparates failures worth a business retry from failures that should start compensation.Activitiesis a protocol rather than a concrete dependency. That makes it easy to test, and it keeps vendor SDK details out of the workflow logic.order_workflow(...)coordinates the saga.- The opening check turns away plainly bad input before any side effect. It is typed validation at the workflow's edge.
- Waiting for approval creates a pause that must survive restarts. In production, a timer and stored state back it, not a bare function call.
payment_key = f"pay:{order.id}"guards the charge. A retry, a worker crash or a redelivered message cannot trigger a second one.activities.charge(...)touches the outside world. The orchestrator reads its return value as a receipt; the payment ledger remains the source of truth.- After payment, the saga moves on to
update_crm(...)and thensend_receipt(...). except PermanentErrorcompensates by issuing a refund.- Raising the error again keeps the failure visible. The orchestrator can then record the terminal state and show it in history.
🎯 Interview Pointer: Expect a question on how fine-grained the idempotency keys are, and answer it head-on. Each business action with a side effect gets its own key (
pay:,crm:,mail:,refund:). The workflow as a whole does not share one. That single detail moves an answer from a whiteboard sketch to something a team could run in production.
What the Whiteboard Version Omits on Purpose
- The short snippet leaves out concurrency control, versioning and observability hooks. A real build needs all of them:
- Optimistic concurrency: each
WorkflowInstanceupdate checks a version number, so two workers cannot both move the same instance forward from stale state. In practice: "update where version = expected_version"; if another worker got there first, retry or abort. - Schema and contract versioning:
definition_versionon the instance lets old and new definitions run at the same time. An instance in flight stays on the version it began with, unless an explicit migration policy says otherwise. - Idempotency at every write boundary: payment is only one case. Repair actions, email sends, CRM updates and approval signals should be just as safe to replay, where that is feasible.
- Observability: every transition should produce metrics, trace spans and structured logs, tagged with step name, outcome and
instance_id. The history table is the system of record. Telemetry is there for fast diagnosis. - Policy checks around model output: an LLM (large language model) might classify, route or summarise work inside a step. Before that output can change state or fire an external action, it must clear typed validation and policy checks. The model may propose; it must not quietly decide.
- Optimistic concurrency: each
Tests That Make the Design Believable
- A contract test checks that a repeated start yields one instance. Send
POST /v1/workflows/{type}twice with an identical idempotency key, then assert that both responses carry the same instance id. - A failure-injection test runs the key drill: a fault after payment has gone through.
charge(...)succeeds; the CRM update then fails permanently. The workflow should refund. Replaying from history should then find the payment already made and skip the charge. A true crash-and-resume test, which kills the worker between the two steps, works the same way: replay reads history, skips the charge and picks up at the first step not yet done. The rule behind both: the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does.
import pytest
@pytest.mark.asyncio
async def test_start_workflow_is_idempotent(client):
payload = {
"workflow_type": "order_approval",
"business_payload": {"order_id": "o-123", "amount_cents": 5000},
"correlation_id": "corr-1",
}
headers = {"Idempotency-Key": "start:o-123"}
first = await client.post("/v1/workflows/order_approval", json=payload, headers=headers)
second = await client.post("/v1/workflows/order_approval", json=payload, headers=headers)
assert first.status_code == 201
assert second.status_code in (200, 201)
assert second.json()["instance_id"] == first.json()["instance_id"]
@pytest.mark.asyncio
async def test_worker_crash_after_payment_does_not_double_charge(orchestrator, fake_activities):
order = Order(id="o-123", amount_cents=5000, customer_email="a@example.com")
async def approval(order_id, timeout_days):
return True
async def charge_succeeds(*args, **kwargs):
return PaymentReceipt(idempotency_key="pay:o-123", external_ref="payref-1", status="captured")
fake_activities.charge.side_effect = charge_succeeds
fake_activities.update_crm.side_effect = PermanentError("crm unavailable")
with pytest.raises(PermanentError):
await order_workflow(order, fake_activities, approval)
# On replay, the orchestrator should consult history/receipts and not charge again.
await orchestrator.replay_from_history(order.id)
assert fake_activities.charge.await_count == 1
assert fake_activities.refund.await_count == 1
- Together they prove two things. The API contract is idempotent in practice, not only on paper. And the most important drill in this design holds: a failure after a successful payment ends in a refund, and replay never charges twice.
Why This Answer Sounds Credible in an Interview
- Weak answer: "We'll add retries, and that makes it reliable."
- Strong answer: workflow state belongs to the orchestrator. Each side effect carries an idempotency key. Each state transition is versioned. Each replay is driven by history. Compensation is spelled out, never implied.
- With those parts made concrete, the rest of the system can be explained rather than taken on faith. That gap separates someone who can describe an orchestration system from someone who can build one.
Tutorial 6 · 9 min — The full lesson: how to reason through this case from first question to rollout.
6. Security, Reliability, and Failure Handling
The Uncomfortable Question That Anchors This Section
- Picture the design review. Security, operations and the customer's admin are all in the room, and within minutes someone asks the hard one: the payment went through, then the worker died. Now what?
- That one injected fault teaches more than twelve questions about the happy path. It shows where the system's real edges lie.
- Think about what the orchestration layer protects: money movement, approval state, messages to customers and the integrations further downstream. It must keep each of them safe from half-finished runs, replays, operator mistakes and hostile access. A design that cannot get through this drill is not yet a workflow system. It is a script with a database behind it, running on best effort.
Threat Model Before Failure Policy
- The design rests on four security controls:
- Authorise starts, signals, and repairs at the correct strength. Anyone who starts a workflow needs at least the permission required to read the customer object that workflow will change. Each signal (approval, rejection, resubmission, cancellation, override) must carry authentication, be limited to the right tenant and workflow instance, and pass a check against the current state machine. Repairs need the tightest control of all. They can skip the normal sequence and change a business effect after it has already happened.
- Keep secrets out of history. History is how the system remembers, which also makes it a target. A token, API key, password or full payment artifact stored there can be replayed or exported. It can also be shown to operators who need to see state but have no reason to see credentials. The safer pattern keeps only opaque references in history. At execution time the worker pulls a short-lived secret from a secret manager or a KMS-backed vault, with the narrowest scope and a clear expiry.
- Restrict operator mutation paths. An operator's job is to pause, inspect and resume. It is not to rewrite state transitions at will, or to quietly record money as collected. Any manual change goes through a narrow repair API that validates input, checks versions and logs the approval. This is defence in depth: authenticated admin access, permission checks based on role, immutable audit logs and state-machine rules each guard one boundary from a different side.
- Audit every compensation and manual override. Reversed payments, voided document approvals and hand-repaired CRM (customer relationship management) records must all leave a trail. Record who started the action, the reason, the instance it touched and the state version it aimed at. With that evidence, the post-incident review rests on facts rather than guesses.
Failure Policies Are Design Decisions, Not Afterthoughts
- In the interview, say out loud which branches fail open, which fail closed, which degrade, which queue and which need a person. Using those words with care is itself a sign of judgement.
- Here is where the common branches land:
- Fail closed: approval completion, payment authorisation, repair mutations, and any step that could record a business effect that never really happened.
- Degrade: if the customer is happy with eventual delivery, email can fall back to queued sending or a later notice.
- Queue: CRM sync, internal analytics updates and noncritical enrichment can wait in line while a transient outage clears.
- Human intervention: approvals that are unclear, poison messages that keep failing, and policy exceptions that code cannot settle safely.
- Fail open only with extreme caution: occasionally acceptable for a read-only status view; never acceptable for an external side effect that cannot be undone.
- Treat blast radius as a working control. Partition every retry queue, dead-letter stream and repair lane by tenant, region, workflow type and dependency. Then a stuck CRM connector cannot block document approvals for tenants it has nothing to do with. Likewise, a regional outage must not quietly spill over a region line unless both the customer contract and the data policy say it may.
Failure Drill: Worker Crashes After Payment Succeeds
- Detection: the lease expires, heartbeats stop arriving, or the orchestrator's own timeout fires.
- Containment: never send the charge a second time.
- Recovery: replay the history, see that the charge step finished, and pick up at the first step that has not.
- Prevention: put an idempotency key on the payment request. Keep a durable history that writes the intent before the call and the completion once the acknowledgement arrives.
- Of all the incident drills, this one matters most. Security and operations inject the crash. The candidate has to show how the system limits the damage and keeps the evidence intact, not how they would scramble to get things running again.
🎯 Interview Pointer: If you are asked what happens when the worker dies after the payment goes through, build the answer on one sentence: the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does. A lost worker is not a lost business action. Tie detection, containment, recovery and prevention back to durable history every time.
Failure Drill: Email Succeeds but CRM Fails
- Detection: track side effects per step rather than per job. The email worker may report success while the CRM update times out or comes back with a permanent error.
- Containment: rather than pretend the whole process worked, the workflow moves into a state that can be compensated or repaired.
- Recovery: retry the CRM call idempotently, hand the item off to dead-letter handling, or open a manual reconciliation task when a person must validate the CRM record.
- Prevention: keep a status for each step and carry explicit correlation IDs. Set a policy that workflow state is authoritative and notifications are not.
Failure Drill: Approval Waits Seven Days
- In orchestration, a long wait is ordinary, not exceptional. The system has to provide durable timers, state that people can see, and a way to deal with stale instances.
- Detection: this raises no failure alarm. A timeout threshold simply routes the instance to the queue that should act on it.
- Containment: do not burn worker capacity on polling that never ends.
- Recovery: depending on policy, send the reminder again, escalate to a manager, or let the request expire.
- Prevention/design intent: waiting is a first-class state in the workflow, not a sleep loop improvised for the occasion.
Failure Drill: Definition Changes While Instances Run
- Treat this as versioning, not merely as a deployment concern.
- Detection: the runtime notices that the definition hash or version does not match.
- Containment: each old instance keeps running on the version it began with, unless someone has defined an explicit migration path.
- Recovery: either route steps by version so old instances stay forward compatible, or run a controlled migration that is tested and audited.
- Prevention: each durable instance stores its definition version. Each new deployment either publishes a compatible contract or declares a deliberate break.
Failure Drill: External API Times Out Indefinitely
- Four things are needed here: timeouts with a bound, retries that back off, a circuit breaker, and a route to dead-letter handling or escalation.
- Detection: no progress inside a known limit.
- Containment: stop pounding the dependency, and shield the worker pool.
- Recovery: a scheduled retry, a fallback queue, or a person stepping in when the downstream system stays down for a long stretch.
- Prevention: put a time bound on every call to a dependency. Have the orchestration policy treat transient and permanent failures differently.
Evidence the Interviewers Want to Hear
- Before go-live, you should be able to walk through the audit evidence and the runbooks. Who may start a workflow? How does someone ask for a repair, and who signs it off? Where do compensation logs live? How does support get a replay explained to them? How do you prove that a crash did not lead to a duplicate payment?
- Make that evidence searchable by tenant and workflow id. Keep it for as long as the customer's policy and jurisdictional rules require.
- For observability, trace each transition with correlation ids. Emit counters for retries, compensations, dead-letter items and manual overrides. Raise an alert when the counts of requested, completed and compensated steps drift apart.
- Knowing that something is broken is not enough. You want to know which tenant, which dependency and which failure policy is soaking up the impact right now.
Interview-Sized Code Sketch: No-Double-Charge Invariant
- The test is narrow but it matters: crash the workflow at the worst moment, and replay must still not repeat an irreversible side effect.
- Treat this as a teaching sketch sized for an interview. It leaves out persistence details, dependency wiring and the full workflow engine. A production build would put the event log on durable storage, add validation and pin library versions.
import pytest
class FakePayments:
def __init__(self):
self._charges = {}
def charge(self, order_id: str) -> str:
self._charges[order_id] = self._charges.get(order_id, 0) + 1
return f"charge-{order_id}"
def charge_count(self, order_id: str) -> int:
return self._charges.get(order_id, 0)
class CrashAfterCharge(Exception):
pass
class WorkflowEngine:
def __init__(self, payments: FakePayments):
self.payments = payments
self.history = []
self.completed = set()
async def run_until_crash(self, order_id: str, crash_after: str | None = None):
if "charge" not in self.completed:
self.history.append(("intent", "charge", order_id))
self.payments.charge(order_id)
self.history.append(("done", "charge", order_id))
self.completed.add("charge")
if crash_after == "charge":
raise CrashAfterCharge()
async def resume_workflow(self, order_id: str):
if "charge" not in self.completed:
self.history.append(("resume", "charge", order_id))
self.payments.charge(order_id)
self.history.append(("done", "charge", order_id))
self.completed.add("charge")
self.history.append(("next", "crm", order_id))
@pytest.mark.asyncio
async def test_crash_after_payment_does_not_charge_twice():
payments = FakePayments()
engine = WorkflowEngine(payments)
order_id = "order-123"
with pytest.raises(CrashAfterCharge):
await engine.run_until_crash(order_id, crash_after="charge")
await engine.resume_workflow(order_id)
assert payments.charge_count(order_id) == 1
- What it teaches: the workflow has to keep enough state to tell "attempted" apart from "completed". Recovery must check that state before it decides to repeat the side effect.
- A production version would also validate arguments, persist state, log in a structured format, handle timeouts, and pass an explicit idempotency key through to the payment gateway request.
What to Say When the Interviewer Pushes on Trade-offs
- Customer wants faster recovery → automate more of the compensations, and accept a higher chance that an automated undo gets it wrong.
- Business wants stronger correctness → fail closed more often, and accept deeper queues and more work for people.
- Platform has to serve many tenants → draw sharper blast-radius boundaries and tighten operator permissions.
- None of these is a footnote. Taken together, they are the architecture.
- The production judgement signal: an FDE (Forward Deployed Engineer) is accountable for safe rollout, support and incident response, not just the happy path. A good answer keeps the customer's business effect safe, holds on to evidence for the postmortem, and still pushes the workflow forward without repeating any irreversible action.
Tutorial 7 · 12 min — The full lesson: how to reason through this case from first question to rollout.
7. Delivery Plan, Observability, and Business Impact
Start with One Workflow, Not the Whole Platform
- The prototype runs, so the customer asks: when can we rely on it in production? The design talk now becomes a delivery plan, with measurable gates, a named owner per step and a route into support.
- Begin by modelling a single workflow in explicit form. A good candidate is document approval, then payment, then the email, then the three internal updates.
- A slice this narrow tells you whether business effects survive retries, pauses and crashes. It also keeps the first release small, so the team can reason through every move from one state to the next.
- Breadth of features is not the aim here. Learning is. You want to confirm three things: the workflow definition holds steady, anyone can inspect the state model, and the team can say what happens when a worker dies after the card is charged but before the email goes out.
- When you name that first workflow out loud, you signal that you think in MVP (minimum viable product) terms and stage the rollout. The alternative is one big, risky launch.
Determinism and Crash Recovery as the First Gate
- Test two things before any traffic grows:
- Feed a recorded workflow history back through the engine. Check that every decision comes out the same as before.
- Kill workers in the middle of a run and bring them back. Check that the engine picks up from durable state and does not blindly fire the side effects again.
- This pair is the first true go/no-go gate. It shows whether the orchestration logic lives through the failures that hurt most.
- A line worth having ready: "I won't promote this until we can replay a run from its history, recover from a worker crash, and show that no irreversible effect happens twice."
🎯 Interview Pointer: Put deterministic replay and crash recovery first in the gate order, ahead of operator tooling and ahead of a wider release. That ordering marks a seasoned delivery plan. A weaker answer just promises to "add monitoring later".
Operator Visibility Before Broadening Scope
- With recovery proven, give operators a window into the system: dashboards, logs, traces and admin screens. Support staff should be able to answer three things quickly. What is in flight? What has stalled? What needs a human?
- This is core, not polish. Without it, broken work piles up quietly; with it, a support team can actually run the platform.
- A rule that works in practice: show workflow-level state in the words the business uses (pending approval, payment authorised, compensation pending, completed). Beside it, show activity-level signals such as retry counts, timeouts, failed workers, how deep each queue is and how slow downstream calls are. Link the two, so a complaint from a user leads straight to the step that failed.
Version Workflows Instead of Mutating Them
- Ship changes as a new workflow version. Never edit running executions in place. A run in flight may wait for days on an approval, a human reviewer or an outside system. If you swap its logic while it waits, old and new instances start to behave differently.
- Versions keep the operating model easy to follow. A run keeps the rules it began with. New runs get the new logic. During an incident, operators know which history applies.
- Here the customer begins to trust the platform. The way changes ship has become predictable.
Rollout Scorecard: Six Metric Layers
- Strong FDE (Forward Deployed Engineer) candidates do not squash every number into "the system is up". Their scorecard has six separate layers.
Technical health metrics
- Workflow completion time: elapsed time from a run's start to its terminal state. Data comes from orchestration history and timer records. The platform team owns it. Alert when p95, or a threshold agreed with the customer, moves well above the baseline.
- Activity retry rate: retries over total activity attempts. Data comes from worker and scheduler telemetry. The worker-runtime owner is responsible. Alert on a sudden spike, or when the rate stays high over a rolling window.
- Stuck workflow age: how long the oldest non-terminal run has waited past its expected time. Data comes from durable state and inspection of the queues. Operations owns it. Alert as soon as any run passes the maximum age the team will accept.
Model quality metrics (whether the orchestration model itself stays correct and stable: do the workflow definition, the replay behaviour and the state transitions still match the business logic they were meant to encode?)
- Replay determinism pass rate: the share of sampled histories that produce identical decisions on replay. Data comes from replay test jobs and checks on history. Workflow-runtime engineering owns it. Alert when it drops under the release gate, because nondeterminism can break the whole model.
- State-transition validation failures: how many invalid or surprising transitions tests, canaries or runtime guards catch. Data comes from the engine's validation and canary telemetry. The platform team owns it. Alert on any rise that persists, because it hints that the model or the versioning rules have drifted.
- Schema or definition compatibility failures: how many definition changes old or new runs cannot load safely. Data comes from versioning checks and validation at deploy time. Release engineering owns it. Alert if compatibility fails in staging or canary, because every version of the model has to stay loadable.
Reliability and correctness metrics
- Duplicate effect count: external effects caught happening twice, such as a repeated payment attempt or a second copy of an email. Data comes from idempotency logs, receipts from downstream systems and reconciliation jobs. The workflow platform and the integration owner share it. Alert on any confirmed rise, because even a handful can be costly.
- Compensation rate: the share of runs that end up taking a compensating action. Data comes from workflow history. Product and operations own it together. Alert on an unexpected climb, which can point to a bad release or a brittle dependency.
Operational burden metrics
- Manual repair count: runs that needed a person to step in. Data comes from operator actions and support tickets. Support operations owns it. Alert when it outgrows what the team can handle, or keeps climbing from one release to the next.
Adoption metrics
- Track workflows that real customers start, the share of teams that have migrated and how often operators log in to the visibility tools. These count because an elegant orchestration platform that no one trusts has failed as a product.
Business outcome metrics
- Look for fewer failed approvals, a shorter gap from payment to email, fewer customer escalations and a shorter cycle for the whole process. The point to make in the interview: technical health tells you the machine works; business outcomes tell you the customer gains from it.
Phased Rollout: Owners, Exit Criteria, Rollback Triggers
- Give every phase a named owner, clear exit criteria and a trigger for rolling back:
- Model one workflow explicitly
- Owner: a platform engineer, paired with someone from product or a design partner on the customer side.
- Exit criteria: the definition has settled, each state transition is written down and everyone agrees on the business effect.
- Rollback trigger: the first workflow cannot be modelled cleanly, or the customer disputes what success means.
- Test replay determinism and crashes
- Owner: platform engineering, with QA (quality assurance) or release engineering.
- Exit criteria: crash-recovery tests pass, replays give steady results and failure drills show no duplicated side effects.
- Rollback trigger: some crash path repeats an irreversible action that has no idempotency guard.
- Add operator visibility
- Owner: operations, working with the orchestration team.
- Exit criteria: operators find stalled work, explain its state and carry out approved repairs from written runbooks.
- Rollback trigger: support cannot diagnose an incident without pulling in engineering.
- Migrate workflows by version
- Owner: the platform owner and the release manager.
- Exit criteria: old and new versions run side by side safely, the migration rules are written down and no in-flight run is rewritten.
- Rollback trigger: a versioned migration gives inconsistent results or gets in the way of recovery.
- Model one workflow explicitly
- A real go/no-go gate never asks whether the code compiles. It asks three things. Can support run it? Can we execute the rollback? Can the customer live with the risk that remains?
Connect the Dashboard to the User Journey
- A good dashboard reads like the business story, from left to right, for someone who is not an expert. The request comes in, approval is pending, payment is authorised, internal systems get their updates, the email goes out and the run closes.
- Beneath each status a user would recognise, put the matching component signals: retries, latency, queue age and compensation state.
- Triage gets faster, because support can see where the story went off course. The customer also sees that the platform is no black box. Visibility then becomes part of what they are buying, not only a tool for operations.
Core Product vs Adapter vs Configuration vs Shared Service
- The core orchestration engine should not hold everything:
- Core product: durable workflow state, retry policy, compensation orchestration, replay, versioning and the building blocks of operator visibility. These are the reusable behaviours the platform ought to own.
- Adapters: the payment gateway, the email providers, document systems and the three internal systems, each reached through its own adapter. They sit behind interfaces because they differ from one customer and environment to the next.
- Configuration: retry budgets, timeout windows, rules for routing approvals, escalation thresholds and visibility settings per tenant. These change more often than code does, so it must be safe to adjust them.
- Shared services: identity, audit logging, metrics export and notification plumbing, where the organisation already has a standard for them. Reusing them cuts duplication, but only if the team can keep ownership lines and support commitments intact.
- The split shows product judgement. You are not building a bespoke workflow engine for a single customer. You are pulling reusable product value out of the pilot.
Train Operators Before the First Wide Rollout
- Being ready for production means having trained people and written guidance.
- Support needs a runbook for the usual failures: a worker crashes after payment, an outside call times out during approval, an upstream system sends the same callback twice, a run sits waiting on a human reply.
- The docs should explain how to read state, when and how to escalate, and which compensations an operator may safely trigger by hand.
- Teach through scenarios, not slides. Take each operator through a stalled run, a clean replay and a call on whether to roll back. If they cannot explain a workflow from the dashboard and the runbook, the system is not ready for wide use.
The Risk Register Should Be Owned, Not Implied
- Keep the risk register short; it stops the rollout drifting into wishful thinking. Every entry has an owner, a mitigation and a trigger:
- Worker crash after payment succeeds: platform engineering owns it. Idempotency keys, durable state and replay tests mitigate it, because the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does. Trigger: a payment event with no terminal workflow record.
- Replay nondeterminism: the workflow-runtime team owns it. Deterministic APIs and replay tests driven by history mitigate it. Trigger: a replayed decision that differs from the recorded one.
- Operator overload: operations owns it. Dashboards, runbooks and tuned alerts mitigate it. Trigger: the manual repair count keeps going up.
- Version migration error: the release manager owns it. Version gating and a canary rollout mitigate it. Trigger: old and new runs reach different outcomes.
- This is not paperwork. The register is how a technical design becomes a service people can support.
- A strong rollout story goes in order. Prove one workflow end to end. Next, check replay and crash recovery. Then give operators visibility. Finally, migrate by version behind a canary and a rollback plan. Through every step, keep an eye on six numbers: completion time, retries, how long work sits stuck, duplicated effects, compensations and manual repairs. Hold off on calling it a success until customers use it, the business process gets better and support can run it without heroics.
- The customer impact you can measure: a workflow platform that can apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails. Recovery stays under control and manual work drops. Approval, payment, email and the internal updates then run as one dependable service, not a chain of brittle one-off scripts.
Tutorial 8 · 13 min — The full lesson: how to reason through this case from first question to rollout.
8. Interview Walkthrough, Trade-Offs, and Practice
Minute-Zero Opening: Lead with the Business Effect
- Sound like an FDE (Forward Deployed Engineer) who is already in the room with a customer under production pressure. Do not sound like a candidate listing patterns. Start from the outcome and flag the riskiest assumptions early. Then keep steering back to three things: the business effect, how the system recovers, and what control operators keep.
- Model opening: "Approval of documents, a payment, an email and three internal systems all have to move together, and any one of them can fail or sit waiting for days. My goal is to apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails. First I'll pin down what a successful business effect means here. Next I'll size the workflow volume. Then I'll propose the orchestration model, the storage and the recovery approach. I'll finish with failure handling, security and rollout."
- That opening earns four things in one go. It proves you grasp the customer's need, centres the design on business effect rather than plain task execution, and signals a plan for the hour. It also lets the interviewer steer you if their setting has a different constraint.
A Practical 50-Minute Answer Plan
- Spend minutes where the risk is, not where the diagram is biggest:
- 0–5 min: discovery and assumptions. Find out what a finished workflow looks like. Ask which steps a person does and which are automated, whether an approval can take hours or days, and which system owns the truth about state. Say your assumptions aloud and ask to be corrected.
- 5–10 min: scope and scale. Estimate how many workflows are active, how long each runs on average and how many instances peak at once. Add the extra writes that history and retries create. Use that to choose between a simple queue-based engine and a sturdier, durable workflow service.
- 10–20 min: architecture. Sketch the orchestration boundary, the workflow state store, the worker pool, idempotent activity executors, compensation handlers and the external systems. Make clear that the orchestrator owns ordering and durable state, while workers carry out the side effects.
- 20–28 min: failure modes. Step through four cases: a worker crashing after payment, a timeout while approval is pending, a webhook delivered twice, and a run that completes only partly across internal systems. For each, explain which guard stops the duplicate: the event history, the retry policy, the idempotency keys, or a compensation or escalation path.
- 28–35 min: security and controls. Walk through who the caller is at each hop (identity propagation), least privilege, secret handling, audit logs, retention, and what you redact or minimise. Each control should answer a named failure mode or compliance need.
- 35–42 min: product and operational leverage. Talk through observability, dashboards for operators, how stuck workflows get handled and how in-flight workflows are versioned. Show that one platform can serve many business processes, so it does not turn into a factory for one-off scripts.
- 42–47 min: trade-offs and alternatives. Weigh an orchestrated saga against choreography, history size against debuggability, automatic compensation against human review, and workflow code against declarative definitions.
- 47–50 min: close with a concise executive summary. Come back to the customer outcome, the architecture, the largest trade-off and the gate for rollout.
Keeping the Conversation at the Right Depth
- Candidates often lose the hour drawing boxes and leave the fragile edges almost untouched.
- Let the risk profile set how deep you go:
- Approvals that can wait a week → long-lived state and versioning outweigh small latency optimisations.
- Payments that cannot be undone → idempotency and compensation outweigh clever orchestration syntax.
- Several internal systems that are only eventually consistent → event ordering and reconciliation outweigh the precise choice of queue.
- State your assumptions and give the interviewer a chance to change course. For example:
- "My assumption is that the workflow engine keeps a history per step and can pick up again after a crash. If a simpler build-versus-buy view would help, I can lay that out as well."
- "Workers may see a task more than once, so I'll make every action that changes something idempotent."
- "My assumption is that the business wants state it can see and audit. So the orchestrator stays the source of truth for progress."
- Framed this way, you pick the smallest design that stays safe under realistic growth and failure, rather than building for every imaginable future.
Trade-off Framing to Defend
- Orchestrated saga vs. choreography.
- Orchestrated saga: one control plane holds state transitions, retries, compensation and visibility. That suits long-lived, audit-heavy processes that have to ride out worker crashes and manual steps. The cost is a central dependency that needs high availability and careful versioning.
- Choreography: services share the work by reacting to events. Central coupling drops, and ownership of each service can look tidier. The catch: nobody can easily tell where a given run has got to, or what halted it. Reasoning about cross-service compensation and human review gets harder too.
- Verdict: for a small event-driven system, choreography can appeal. Once the customer wants state they can see, retries under control and recovery that support can run, orchestration is easier to operate.
- History size vs. debuggability.
- Keeping every transition, input, output and retry helps debugging, because you can replay the path a decision took and see exactly where it failed. The price is growing storage, heavier reads and more care with redaction.
- Cutting history back saves overhead, yet it throws away the forensic record that operators and support depend on.
- Verdict: "keep what you need to replay and explain each decision, compress or summarise older segments once it is safe, and hold the audit trail as long as customer policy says". The answer is not "keep everything forever."
- Automatic compensation vs. human review.
- Automatic compensation shines when the undo is deterministic and safe. Examples: cancel a reservation, void a pending authorisation, flag a record as failed, or send a correcting notification.
- It is weaker once a side effect is irreversible, unclear or regulated. There, a queue for human review may be the better fallback.
- The tension is speed against correctness. Full automation cuts latency and manual effort, but it can magnify a wrong call. Human review is slower, yet legal, financial or high-risk operational lines may demand it.
- Workflow code vs. declarative definitions.
- Code allows rich branching, shared helper logic and intricate compensation flows. In return it brings harder versioning and more testing.
- Declarative definitions are simpler to inspect and validate, and people who are not engineers can follow them. In return they can prove too rigid for the branching real enterprises need.
- Balanced position: offer a declarative model for the common shape, and let code handle business-specific logic where it has to.
🎯 Interview Pointer: Expect "orchestration vs. choreography" to be the trade-off an interviewer pushes hardest, often as a blunt "why not let events drive it all?" Have a one-line verdict to hand. Choreography lowers coupling, but nobody can then point to where a run stands, and that rules it out once the customer needs visible state and recovery support can run.
Follow-up Drills and Strong Answers
- "What if payment succeeds and the worker dies?" Protection comes from idempotency plus durable workflow state, not from hoping the worker lives. Say the charge went through and the worker crashed before it recorded completion. The orchestrator replays the workflow and sees the payment step already committed. It then either carries on from the persisted state or checks with the payment provider using the idempotency key or transaction reference. In short, the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does. A restarted worker must never cause a second irreversible payment. Workers are allowed to die; the workflow is not allowed to forget.
- "How do you change code for running workflows?" Shipping new code and crossing your fingers is not an answer. In-flight runs need explicit versioning rules. One route is backward-compatible definitions. The other pins every new instance to a versioned definition, while older instances finish on the logic they started under. When a change has to reach in-flight workflows, use a deliberate migration path, a compatibility layer or a controlled cutover policy. Describe the operating routine too: try the new version on fresh instances, canary it, check the outcomes, then widen it.
- "What is the idempotency boundary?" It sits at each side-effecting business action, not around the whole workflow. Give every effect the outside world can see its own idempotency key: submitting a payment, sending an email, writing to an internal system, posting an approval record. Match the boundary to how much harm a duplicate does in the external system. Payment is irreversible, so it gets the strictest discipline. Email is a notification, so it needs duplicate suppression but can be looser.
- "How do you handle a week-long approval?" Approval is a stored, durable wait; no thread stays busy for it. Persist the state, set a reminder or SLA (service level agreement) timer where needed, and resume once the approval event comes in. When it runs late, escalate it, reassign it or move it to a manual review lane. For waits like this, never lean on in-memory timers or worker leases.
Common Weak Answers and How to Repair Them
- "I'd just retry until it works." → Separate transient failures from permanent ones. Add limits and backoff, plus a clear dead-letter or human escalation path.
- "The worker can keep the state in memory." → Workers crash, get redeployed and get scaled away. Anything that runs for days must persist its progress.
- "We can always compensate later." → Some effects cannot be undone. Name the irreversible steps and set out the fallback for each.
- "Choreography is more scalable." → Scale on its own does not settle it. Talk about observability, recovery and how support will run it.
- "One generic retry policy is enough." → A payment, an approval and an email fail in different ways. Give each its own retries and alerts.
- "We'll change the code and restart everything." → Pin versions on live runs so instances already under way still work.
Scoring Rubric for Self-Evaluation
- Discovery: Did the candidate find out what "done" means, which steps people do and which effects cannot be reversed?
- Estimation: Were concurrency, duration and retry pressure sized sensibly, even with illustrative numbers only?
- Architecture: Was there a clean account of control flow, durable state, how workers behave and the external integrations?
- Depth: Did crash recovery, idempotency and versioning get more time than drawing boxes?
- Security: Were least privilege, secret handling, audit trails and keeping data to a minimum all raised?
- Delivery: Were rollout, tools for operators and the handoff to support covered?
- Communication: Was the answer structured, were the assumptions explicit, and did it close with an executive summary?
- A strong answer has a clear structure and uses numbers where they count. It treats side effects safely, names its trade-offs openly and keeps returning to the customer outcome.
A 90-Second Architecture Summary
- A close you can give at minute 50: "A durable workflow orchestrator is the source of truth for progress, and every business effect sits behind its own idempotent step. The orchestrator stores state and event history. That lets it survive worker crashes, wait out long approvals and pick up again without repeating irreversible actions such as payment. People review only the cases where compensation is unsafe or unclear. I'd version the workflow model so running instances keep going safely. Observability and operator tooling come early, so support can tell where any run is blocked. The riskiest trade-off is orchestration against choreography. Choreography is simpler across services. For a long, customer-facing process, orchestration wins on debugging, recovery and support. My first rollout gate: before wider adoption, a single end-to-end workflow must prove replay, crash recovery and visible state in a production-like setting."
- Reuse that summary as an artifact. Its parts: the durable orchestrator as source of truth, idempotent steps, surviving crashes and long waits with no repeated irreversible action, human review only for unsafe or unclear compensation, a versioned model, observability and operator tooling first, and the riskiest trade-off named next to the first rollout gate.
Practice Loop: Solo, Pair, Implementation
- Solo exercise: run the 50-minute plan out loud, then answer the four follow-ups with no notes: the payment crash, the version change, the idempotency boundary and the week-long approval.
- Pair mock: a partner cuts in with the events-only challenge from the Interview Pointer above. Defend orchestration, and never repeat a sentence.
- Implementation exercise: sketch, by hand, the states a workflow moves through. Then trace a crash at the precise moment the payment has succeeded but its state is not yet acknowledged. Explain each step: how the system detects the completion and how it blocks a duplicate effect.
- Give that answer without rushing, and the interviewer sees someone who can run a customer's multi-day process live. They also hear you explain, with no hand-waving, how the process stays trustworthy when reality knocks it off the happy path.
Tutorial 9 · 11 min — The full lesson: how to reason through this case from first question to rollout.
Coverage Notes
We scored this tutorial twice against our fixed 20-item decomposition rubric. On the second review the weak spots got smaller but did not disappear. Two still fall short: how much each workflow costs (the cost SLI (service level indicator) and cost-per-workflow wording) and how widely the design deals with regulation and compliance. The list below marks both as Partial.
Phase 1 — Problem Framing & Discovery
- Item 1 (turning a feature request into a business outcome): Full. Section 1 opens by stating the outcome first.
- Item 2 (who the stakeholders and personas are): Full. Section 1 maps each stakeholder to the job they need done.
- Item 3 (questions whose answers reshape the architecture): Full. Section 2 lays them out as a question tree.
- Item 4 (splitting and ranking requirements): Full. Section 2 sorts them into must, should and could, and separates functional from nonfunctional.
- Item 5 (a clear fence around scope): Full. Section 2 lists the non-goals under "What the MVP (minimum viable product) Will Not Support."
Phase 2 — Estimation & Architecture
- Item 6 (rough sizing of scale and capacity): Full. Section 3 estimates workflows, activities, storage and throughput.
- Item 7 (what drives unit cost): Partial. Section 3 counts cost among its six SLIs, yet never works out an infrastructure figure per workflow.
- Item 8 (the architecture and data flow from start to finish): Full. Section 4 gives the component map, the trust boundaries and the sequence diagrams.
- Item 9 (records and API contracts): Full. Section 5 defines four core records and four API endpoints.
- Item 10 (build or buy, vendor and model choice): Absent. Section 8's follow-up drills touch on it once and move on.
Phase 3 — Trade-offs, Security & Reliability
- Item 11 (named trade-offs, each with a fair verdict): Full. Section 8 weighs four pairs.
- Item 12 (threats and the controls against them): Full. Section 6 sets out four security controls.
- Item 13 (drills for failure and reliability): Full. Section 6 runs five named drills, each with detect, contain, recover and prevent steps.
- Item 14 (how we test it): Full. Section 5 has a contract test for idempotency, and Section 6 injects a crash and checks the replay.
Phase 4 — Delivery, Governance & Communication
- Item 15 (metrics in layers, plus observability): Full. Section 7 builds a scorecard with six layers.
- Item 16 (staged rollout, risks and rollback gates): Full. Section 7 plans four phases, each with an owner, exit criteria and a rollback trigger, and adds a risk register.
- Item 17 (depth on regulation and governance): Partial. We handle audit evidence, retention, residency and data minimisation, but we never name an outside framework such as SOX (Sarbanes-Oxley Act) or GDPR (General Data Protection Regulation).
- Item 18 (responsible AI and risks past the obvious failure): Partial. Section 5 mentions policy checks on LLM (large language model) steps in passing and leaves it there.
- Item 19 (adoption and managing change): Full. Section 7 covers operator training, adoption metrics and versioned migration.
- Item 20 (a planned talk track and a way to score yourself): Full. Section 8 gives the 50-minute plan, a scoring rubric, a 90-second summary and a practice loop.
Overall: Of the 20 items, 16 are full, 3 are partial (unit economics, depth on regulation and governance, responsible-AI framing) and 1 is absent (build or buy and vendor choice). The gaps follow where this design puts its weight. It goes deep on durable execution, idempotency and recovery after a crash. Cost modelling and compliance frameworks are not its subject, so we do not invent detail the design does not back up.
My Perspective on the Gaps
What follows is my own view, separate from the design above. It shows one way to close each gap live in an interview, using only the architecture this tutorial has already built.
Item 7: what each workflow costs (Partial).
- I'd get a rough cost per workflow from parts we already have. Section 3 puts raw history at ~400GB in the base case, held as
HistoryEventrows (Section 5). Priced at what AWS lists for US East (checked Sep 2026), it makes a small monthly bill before replication. S3 Standard comes to about $9; gp3 block storage comes to about $32. That is small next to the "manual repair count" line the scorecard in Section 7 already shows. - My bet is that people, not machines, drive the cost here. The formula:
cost per workflow ≈ infra marginal cost + (manual repair rate × loaded operator cost per incident). - Both inputs come from SLIs we already track: compensation rate and manual repair count. So I'd say, "We already have the inputs. We just haven't multiplied them yet." No new metric is needed.
Item 10: building, buying and picking vendors or models (Absent).
- I'd state it plainly. The state machine, the history store and the replay engine form the durable-execution core. Managed durable-execution products already do that job well. For most teams the sensible default is to buy the execution runtime and build the workflow definitions and activities.
- Spend build effort only where we differ from everyone else. That means three things: the compensation policy for each business action (the fail-closed, degrade, queue and human-intervention table in Section 6), where each idempotency key's boundary sits (Section 5), and the saga logic for our domain, which is the
order_workflowfunction itself. - A rule worth saying out loud: buy the commodity execution layer, and let the vendor answer for keeping it durable and up. Build only the business state machine and the policy layer, because no vendor can get those right for you.
Item 17: going deeper on regulation and governance (Partial).
- I'd name the frameworks an interviewer is probably fishing for and pin each one to a part we already built. First, PCI-DSS. If the orchestrator would ever handle card data, push that data to the payment gateway and hold only opaque references. This matches the "keep secrets out of history" control in Section 6 and shrinks PCI scope.
- Second, SOX-style controls, if an approval counts as a financial authorisation. The
ActivityReceiptrecord and the audit-log fields in Section 5 already give a SOX auditor the evidence trail they would request. - Third, GDPR and data minimisation, if an internal system handles customer PII (personally identifiable information). We would enforce it through the retention policy that Section 5 sets on
HistoryEvent. My framing: governance is just rules on records we already keep, not a new stream of work.
Item 18: responsible AI, past the failure everyone expects (Partial).
- Our design touches AI in just one place: before an LLM-driven step changes state, it must pass "typed validation and policy checks" (Section 5). I'd take that further with a concrete case. Suppose an LLM misreads or misroutes an approval. It could quietly approve something a human should have seen. That is an irreversible business effect, the same kind the failure-policy table in Section 6 already tells us to fail closed on.
- So responsible AI needs no new framework. It plugs into the decision table we already have. Treat any model output that could start a payment, finish an approval or trigger a repair as untrusted input. Route it through the same human-review lane we already keep for irreversible or ambiguous compensation.
Key Takeaways
- State the goal as one outcome: apply each business effect of a long-running process exactly once, keep its state visible, and compensate when a step fails.
- Treat payment as the riskiest step because it cannot be reversed, so whatever else degrades, the design must still block a second payment.
- Size from 10 million active workflows and 100 million activities a day, about 1,157 activities a second on average, then plan for peaks of 3x to 10x with at least double the observed peak as headroom.
- Keep workflow state small and park documents and payloads in object storage, so 40 events of 1 KB across 10 million workflows stay near 400 GB of raw history.
- At 10x growth, 1 billion activities a day or about 11.6k a second, expect replay latency, queue hot spots, storage fan-out or cost to break before application-server CPU does.
- Split the control plane (registry, scheduler, timers, signal gateway, compensation engine, operations UI) from the data plane (queues and workers), so decisions scale apart from side effects.
- Only the durable history store owns workflow progress: queues handle delivery, workers suggest effects, and the scheduler rebuilds its view from history each time.
- When the worker dies after payment succeeds, the durable history and the completion receipt decide whether to redeliver or compensate; worker memory never does.
- Give each side-effecting action its own idempotency key (
pay:,crm:,mail:,refund:) rather than one key for the whole workflow. - Keep retry and compensation apart, because capped exponential backoff with jitter only spaces out attempts and only an explicit step such as a refund reverses a charge that already landed.
- Model the seven-day approval as a durable timer state rather than a sleep loop or a polling worker, then remind, escalate or expire it by policy.
- Fail closed on approval completion, payment authorisation and repairs, let email degrade and CRM (customer relationship management) sync queue, and partition retry and dead-letter lanes by tenant and dependency so a stuck CRM connector cannot block other tenants.
- Pin each running instance to the definition version it started with, and ship changes as a new version instead of editing executions in flight.
- Make deterministic replay and crash recovery the first rollout gate, ahead of operator visibility and migration by version.
- Prefer an orchestrated saga over choreography, because choreography lowers coupling but then nobody can point to where a run stands.
Level calibration
On this design the levels separate on how well the answer protects one business effect stretched across days, then across teams, then across years of workflow changes.
| Level | Sounds like | Missing |
|---|---|---|
| Senior | "Append-only history records intent before execution and completion after acknowledgement, each side effect gets its own idempotency key, and the seven-day approval is a timer state. If the worker crashes after payment succeeds, replay sees the charge and moves on; it never charges again." | Retry kept separate from compensation for each step, a pinned definition version per instance, and who is authorised to repair a stuck case. |
| Staff | "Starts, signals and repairs are authorised at different strengths, and repairs go through a narrow audited API. Retry and dead-letter lanes are scoped by tenant and dependency, so a stuck CRM connector cannot block other tenants. Replay determinism and crash recovery are the first gate, then operator visibility and runbooks." | What the core engine owns versus adapters and configuration once a second workflow arrives, and whether partitions survive 10x growth without a rewrite. |
| Principal | "Buy the durable-execution runtime and build the workflow definitions, compensation policy and idempotency boundaries. Core owns state, replay and versioning; payment, email and the three internal systems sit behind adapters. I'd size partitions for the 10x case of 100 million active workflows and about 11.6k activities a second." | The customer's real share of week-long waits and the loaded operator cost per repair, which decide whether history retention or manual repair dominates cost per workflow. |
Sources (checked 27 Sep 2026)
- IETF draft-ietf-httpapi-idempotency-key-header-07 — 400 for a missing key, 409 while the first request is in flight, 422 for a reused key with a different payload
- Datatracker: Idempotency-Key header draft status — expired Internet-Draft, not an RFC
- Stripe API: Idempotent requests — provider-side idempotency keys; keys can be pruned after 24 hours
- AWS Architecture Blog: Exponential Backoff and Jitter — capped exponential backoff with jitter
- AWS Price List API: Amazon S3, us-east-1 — S3 Standard at $0.023 per GB-month
- Amazon EBS pricing — gp3 at $0.08 per GB-month in US East
- PCI SSC: PCI DSS Tokenization Guidelines — tokenisation can reduce PCI DSS (Payment Card Industry Data Security Standard) scope; tokens that can start a transaction may stay in scope
- GDPR Article 5 (EUR-Lex) — data minimisation and storage limitation principles