Practice Worksheet · 3 min — Attempt the case yourself first. Read-only here — use the interactive version to save answers.
1. Interview prompt
As the FDE (Forward Deployed Engineer), your job is to shape a Configurable Platform for Customer-Specific Workflows. Ten customers share one platform, yet each wants its own fields, approval steps, branding and integrations. The thing to avoid is ending up with ten separate forks.
2. Clarify the customer problem
- Which of the differences keep coming back, and which really happen only once?
- Who writes the configuration: our engineers, the customer's admins, or both?
- Does each configuration have to keep working, unchanged, across core releases?
- Where is custom code allowed: in the core, in a sandbox, or only inside adapters?
- How long should it take to configure a new customer?
3. Users and workflows
| User | Workflow | Current pain | AI assist opportunity | Human approval needed? |
|---|---|---|---|---|
| Customer administrator | ||||
| FDE / delivery team | ||||
| Core platform engineer | ||||
| Support and release team |
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
Start from a rendered diagram and shape it to fit this case. Cover each part below:
- Workflow runtime:
- Configuration registry:
- Schema validation:
- Adapter SDK:
- Sandbox and flags:
- Migration and rollback:
7. Evaluation plan
| Metric | Good threshold | Bad threshold | Test dataset | Owner |
|---|---|---|---|---|
| Fork count | 0 | One fork that cannot be removed | Analysis of the repo | Engineering lead |
| Handled by configuration | High and climbing | Dropping | Tags on change requests | Platform eng |
| Time to launch customer | Getting shorter | Getting longer | Records of each release | Delivery lead |
| Tenant incident rate | Stays inside one tenant | Reaches other tenants | The incident system | Support and SRE |
8. Failure modes
- An approval graph contains a cycle and never ends
- An adapter's contract shifts on the downstream side
- An upgrade breaks a field mapping that older configs rely on
- Nobody tested a particular mix of feature flags
- Custom code gets out of its sandbox
- One validation rule eats all the available resources
9. Rollout plan
- Spot the pattern that keeps repeating.
- Build it into shared primitives.
- Have the first customer's admin set it up.
- Run a pilot that goes through the real support desk.
- Record a baseline for launch speed and defect count.
- Widen only while the fork count stays at zero.
10. Weak vs strong answer
Weak: "I'd make a flexible system with forms, approvals and hooks you can configure."
Strong: "I'd run every customer on a single engine that ships on a single release train. Each customer's variation would live in a declarative config that is versioned, immutable and validated before it can be published. I'd deny arbitrary code unless it is explicitly allowed, and scope adapters and secrets to a single tenant. I'd count success as variations shipped with no fork."
11. Candidate scorecard
| Area | 1 | 3 | 5 | Score |
|---|---|---|---|---|
| Problem framing | "Keep it flexible" | Names a shared core | Sets variation without forks as the goal | |
| Architecture | An engine with settings | Registry plus validator | Sandbox, flags and migrations | |
| Evaluation | "The customers are happy" | Tracks incidents | Fork count and share handled by config | |
| Production thinking | Brings on all ten at once | Pilots with one | Rollback, baselines and gates | |
| Communication | Lists features | Mostly clear | Opens with the risk of fragmenting |
Model Answer · 11 min — Compare after attempting the worksheet: the ground a strong answer covers.
Try the worksheet before you read this key. Use it to see what a strong GenAI FDE (Forward Deployed Engineer) candidate asks, designs, measures, secures and explains while taking a demo to production.
Strong discovery questions
- Across the ten customers, which differences keep coming back and which happen only once? The first kind earns a platform capability; the second goes down an exception path.
- Is the variation limited to fields, approvals, branding and integrations? Or does it reach into business logic and the states a workflow can pass through?
- Who writes the configuration: our engineers, the customer's admins, or both, with the vendor signing off before anything reaches production?
- Which integrations must exist on day one? If an adapter fails, does the transition stop, or does the workflow carry on in a reduced mode?
- What do customers expect from an upgrade? Must their configs keep working, unchanged, through every core release?
- Where does custom code stop? Is it allowed in the trusted core, confined to a sandbox, or limited to integration points we have approved?
- How fast must a new customer be configured? A target of hours means self-service tooling. A target of a week leaves room for manual review.
- What should happen when someone misconfigures a workflow? Do we need a fallback approval path that always works?
Strong functional requirements
- Core workflow: ten customers each change fields, approvals, branding and integrations, all on one shared platform and one codebase with no forks.
- Keep a single, stable workflow engine. Every customer runs the same core lifecycle, and every customer ships on the same release train.
- Describe variation as data: schemas, rules and UI metadata. Most customisation should then need no code change at all.
- Offer an adapter interface. External systems plug into it, and the engine itself never needs rewriting.
- Give every configuration a version and validate it. A change can then be reviewed, tested and rolled back.
- Provide migration and rollback tools, so no version change leaves a customer stuck on a config the platform no longer supports.
Strong non-functional requirements
- Latency: split the budget by action. A save should feel instant, a validation preview may take a moment, and a full deployment can run slower as long as it is reliable.
- Availability: this case assumes about 1,000 configuration versions a day, with peaks at 3x. Validation and deployment therefore need separate paths, not one synchronous chain.
- Security: arbitrary code stays off unless explicitly allowed. Adapters and secrets belong to one tenant each. We also check the validation rules themselves, because a rule can be a denial-of-service risk.
- Compliance: keep a lasting record of who published each change, what it altered, which approval gate it passed and which version went live.
- Reliability: isolate configuration by tenant. A setting or test change from one tenant must never change how another tenant's workflows run.
- Cost: track spend per validated version and per activated template. Include the support load that a very flexible primitive brings with it.
Architecture explanation
- Configuration truth lives in the control plane, and workflows execute in the data plane. That separation is what stops the product splitting into ten forks.
- For every tenant, the workflow runtime handles instances, step transitions, timers and retries in exactly the same way.
- Published config versions are immutable. The configuration registry stores them with their release metadata and acts as the system of record.
- On every save and publish, the schema and rule validator runs synchronously. Syntax, semantic constraints, permissions and compatibility all get checked there.
- The UI renderer builds each tenant's forms, labels and layouts from the approved config. Apart from a cache, it stores nothing.
- CRM (customer relationship management), ERP (enterprise resource planning), ticketing and webhook connectors all sit behind one standard interface, the integration adapter SDK. Each tenant's credentials are held by reference.
- Rollout runs through a feature flag service, by tenant, cohort or percentage. The few extensions we approve run in a sandbox with strict limits.
- The migration service moves older configs onto new schema versions. Before a release ships, the tenant test harness replays each tenant's contract tests as a gate.
Data model / integration assumptions
- WorkflowTemplate(id, engine_version, schema); TenantConfig(tenant_id, template_id, version, values); AdapterDefinition(name, contract_version, permissions); ConfigDeployment(tenant_id, version, state).
- ConfigDeployment acts as the deployment ledger. It is the source of truth for the draft, validated, deployed, rolled_back and failed states.
- A rollback adds a new deployment state. It never edits the earlier record, so we can always rebuild the full history.
- Each write states the version it expects to replace. Two operators racing on the same config then cannot overwrite each other without noticing.
- We tag every validation, deployment, adapter test and rollback with the tenant, the config version, a request ID and an idempotency key.
Red-team risks
- approval loops that never end, drift in adapter contracts, upgrades that break old field mappings, flag combinations nobody tested, escape from the sandbox
- An approval graph with a cycle in it keeps looping at runtime. That harm is deterministic, not a transient fault that a retry fixes.
- A downstream service renames a field or changes its response shape. That adapter contract drift breaks every tenant on the adapter at the same moment.
- A tenant upgrade breaks old field mappings, and integrations still pointing at the previous version are left stranded.
- Two feature flags are each safe alone, but nobody tested them together, and the combination opens a bad path.
- Customer-defined logic escapes the sandbox. This is the reason arbitrary code is denied by default instead of only being reviewed.
Rollout plan
- Week 0-1: find out which differences recur, who owns the configuration and where the line for custom code falls.
- Week 1-2: show that one recurring pattern fits into shared primitives, with no one-off fork needed.
- Week 2-3: keep the gate closed until the team agrees that this pattern is stable enough to become a platform capability.
- Week 3-4: have the first customer admin configure their own workflow, then run a pilot through the real support channel.
- Week 5: before going wider, check that the customer accepts both the support model and the rollback procedure.
- Week 6-8: record a baseline for launch speed, defects and validation failures; scope grows only after that.
- After pilot: grow only if configuration is clearly cutting custom work, not hiding rework somewhere else.
Evaluation plan
I picked these thresholds for this case. They are not industry standards, so be ready to justify them rather than recite them.
| Metric | What it proves | Strong threshold | Dataset / method |
|---|---|---|---|
| Fork count | We still ship one product, not ten | No customer-specific branch that cannot be removed | Repo analysis plus architecture review |
| Percentage handled by configuration | Customer differences end up in config, not in code | High, and climbing with each onboarding | Tags that classify each change request |
| Time to launch a customer | Onboarding works as a platform feature, not a bespoke project | Shorter for each new tenant | Timestamps from release tracking |
| Config validation failure rate | People writing config can use the tools, and the guardrails catch mistakes | Steady; a spike points to a confusing primitive | Logs from the config service |
| Upgrade time per tenant | Backward compatibility actually holds | Moving a tenant forward needs no bespoke work | Migration and release records |
| Tenant incident rate | A bad config stays contained | Contained to one tenant and counted apart from platform outages | The incident management system |
Weak answer
I'd build a flexible workflow system where forms, approvals, themes and API hooks are all configurable. It falls short because flexibility with no governance turns into fragmentation. It never says who may write configuration, what an upgrade must preserve, or where custom code has to stop.
Average answer
I'd keep one shared engine, push customer differences into versioned configuration, validate it before publish and put integrations behind an adapter layer. That is an improvement, but gaps remain. It never decides which differences should become platform primitives. It sets no limit on what a validation rule may cost to run. And it has no plan for a tenant left behind on an old schema.
Strong answer
I'd split the feature they asked for, configuration, from the result they need: customer-specific workflows that don't break the product into ten incompatible forks. We should be able to test it on three counts: safe, versioned and supportable. The number I'd track is the share of new customer variations that launch without a fork, a hotfix or an escalation to support. In the design, that becomes one stable engine on one release train. Declarative schemas carry most of the variation, and an adapter interface handles integrations. Configs are immutable, versioned and validated before publish. A narrow sandbox appears only where declarative config really can't reach, and arbitrary code is denied by default. First I'd show that shared primitives can express one pattern that keeps recurring. Then a real customer admin configures and pilots it, and only after that do we expand.
Interviewer scorecard
| Area | 1 - Weak | 3 - Average | 5 - Strong |
|---|---|---|---|
| Problem framing | "Make it flexible" | Mentions a shared core and config | Separates the feature from the outcome; variation with no forks is the measured result |
| Requirements | Lists the configurable fields | Adds versioning and validation | Recurring versus one-off, who writes config, upgrade promise, where custom code stops |
| Architecture | One engine with some settings | Registry, validator, adapters | Control plane and data plane apart, sandbox, flags, migration service, tenant test harness |
| Data/integration | Mentions a config table | Names templates and configs | Deployment ledger, rollback as a new state, writes with an expected version, tagged telemetry |
| Evaluation | "Customers are happy" | Counts incidents | Counts forks, measures the share done in config, launch time and per-tenant upgrade time |
| Safety/security | "Admins are trusted" | Separates tenants | Code denied by default, secrets per tenant, rules checked for DoS, audit of who published |
| Rollout | Onboard all ten at once | Pilots one customer | One recurring pattern first, a real admin pilot, baselines before wider scope |
| Communication | Lists features | Clear, but generic | Opens with the fragmentation risk, states the non-goals, ends on the go/no-go gate |
Final 2-minute spoken answer
I wouldn't open with a list of features. People agree on the headline request and disagree on everything beneath it. So my first job is to pull apart what they asked for, configuration, from what the business needs: ten customers served without the product splitting into ten incompatible forks. I'd make that outcome testable with three words. Safe: no change skips authorisation or creates runtime behaviour nobody reviewed. Versioned: each customer knows which definition is live and what changed in it. Supportable: someone can diagnose a problem and roll a bad configuration back. My own scorecard is simple: count the new customer variations that go live and never needed a fork, a hotfix or a call to support. A count of configurable fields would be a vanity number. The first real design choice is which differences recur. A difference in the approval chain that keeps coming back should become a platform capability. A single seasonal override belongs on an exception path. For the architecture, every tenant shares a single stable engine and a single release train. Declarative schemas carry most of the variation, and an adapter interface handles integrations. Configs are immutable and versioned, and we validate them before publish. A narrow sandbox exists only where declarative config can't go. Arbitrary code is off by default. Adapters and secrets belong to one tenant each, and we check validation rules too, in case one exhausts resources. The failure I'd name is an infinite approval loop. That version fails closed instead of carrying on. My first step is to show one recurring pattern working, and then a real customer admin pilots it.
Tutorial 1 · 13 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
- 4. Architecture and End-to-End Flow
- 5. Data Model, APIs, and Working Code
- 6. Security, Reliability, and Failure Handling
- 7. Delivery Plan, Observability, and Business Impact
- 8. Interview Walkthrough, Trade-Offs, and Practice
- Coverage Notes
1. The Customer Problem and Discovery
Reframing the Request
- The room agrees on the headline, "one platform for similar workflows", and on little else. Fields, approval paths, audit depth and release process all differ by stakeholder.
- So don't start by drawing services. The first move is to turn a vague ask into a measurable outcome: one configurable workflow platform on which ten customers can each change their fields, approvals, branding and integrations, while the shared core stays stable, versioned and supportable.
- The key reframe splits the feature someone asked for ("configuration") from the result the business needs: each customer gets its own workflow, and the product does not splinter into ten forks that cannot talk to each other. Saying that split aloud is what makes you sound like an FDE (Forward Deployed Engineer), not an implementer.
- Make each word in the outcome testable:
- Safe: no change can skip authorisation or switch on runtime behaviour that nobody reviewed.
- Versioned: every customer can see which workflow definition is live and what changed in it.
- Supportable: support and release staff can trace a fault, undo a bad configuration and explain behaviour tenant by tenant.
- Avoid vanity measures such as the number of configurable fields. Track how often a new customer variation ships with no fork, no hotfix and no support escalation.
- Think of it as a job to be done. The customer is hiring the platform to make custom forks unnecessary while each tenant keeps its own process; they are not buying "a workflow engine". That pushes the design towards a stable core with a few controlled extension points, not a codebase that changes shape per account.
🎯 Interview Pointer: Learn the outcome statement by heart: "keep one stable product core, and allow customisation that is safe, versioned and supportable." Interviewers listen for those three words, because each one drives a later architecture decision.
Stakeholder Map
- One request means something different to each group:
- Customer administrators ask who may change a workflow, how much they may customise, and how fast a change reaches users.
- FDE teams want a real customer use case in production without leaving one-off code behind.
- Core platform engineers want a lasting model for workflow definitions, validation, permissions and how the runtime executes them.
- Support and release teams want observability, rollback, a clear incident process and a quick answer to "what changed?"
- Drop a group and the design tilts: without support you ship flexibility nobody can operate; without platform engineers the customer layer never generalises.
- The concrete map, one line per role:
- End user: a customer employee who starts a workflow, approves it, or is on the receiving end of its result.
- Operator: on the support or release team; watches health, reverts a bad config, handles incidents.
- Security owner: answers for permissions, audit and least privilege between tenants.
- Executive sponsor: the business leader paying for one platform rollout instead of a run of forks and long customisation projects.
Discovery Question Tree
- Time is short, so skip the twenty generic questions. Ask the few that remove the most doubt.
- Workflow shape:
- Which parts are the same for every customer, and which really differ?
- Is the variation only in fields, routing, branding and integrations, or does it also touch business logic and lifecycle states?
- Ownership and control:
- Who may write configuration changes, and who signs them off?
- Do customer admins change configuration themselves, or does an FDE or platform team handle every change?
- Risk and recovery:
- What happens when someone misconfigures a workflow?
- Can the customer live with a broken approval path, or must a fallback always exist?
- Success:
- What counts as "done" for the first customer: one live workflow, several teams onboarded, or every variation migrated?
- How will the customer judge it: less manual handling, faster turnaround, fewer errors or simpler audits?
- The answers become the design's inputs: scope, assumptions, risks, owners and success measures.
- If the interviewer holds back, state your assumption aloud: "I'll assume only customer admins can configure, and the vendor approves each change before production." That is a clear boundary, not hedging.
Assumption Ledger
- A useful discovery pass leaves four things on the table:
- Scope: what is configurable and what stays fixed.
- Assumptions: what you accept for now because the prompt left it open.
- Risks: where a bad configuration, a permission slip or a failed integration could hurt a customer.
- Success criteria: the measurable change that shows the platform works.
Weak vs. Strong Answers
- A weak restatement leads with features: "Let's build a flexible workflow system: forms, approvals, themes and some API hooks."
- A strong restatement leads with the outcome: "We need a single shared workflow platform. Each customer can tailor its fields, approvals, branding and integrations without a separate codebase, and the core stays stable enough to version, support and roll out safely." The second version exposes the real tension: flexibility against fragmentation.
- A short spoken opening: "Let me frame it first. One workflow product, ten customers, each wanting its own fields, approval chains, branding and integrations. I want a stable shared core with safe, versioned, supportable customisation, so we never run ten forks. First I'd find out who owns configuration, which variations are really needed, which failures we can't accept, and how we'll measure the first rollout. Then the design rests on validation, rollback and a few controlled extension points, not per-customer code."
- Why it lands: it names the problem and the people, and it holds the architecture back until someone owns the workflow, someone owns the risk and a success metric exists. Bound the problem first; choose the design second.
2. Clarifying Questions, Requirements, and Constraints
Requirement vs. Preference
- The quickest way to lose control of this design is to take "we need flexibility" at face value. Ask who needs it, under what governance, and with what promise about upgrades.
- Sort requirements from preferences and hard constraints from conveniences. Every customer wants a slightly different mix of fields, approvals, branding, integrations and sometimes rules. The first design fork is spotting which differences repeat.
- Skip that, along with who authors configuration and where custom code ends, and you fall into one of two holes. Either the platform is too thin, or you build a bespoke rules engine nobody can support safely.
The Six-Question Interview Tree
- Six questions keep the conversation away from a feature wish list:
- Which differences keep coming back between customers? This separates a shared product pattern from one-offs. Approval chains that differ again and again are a platform capability; one seasonal override goes down an exception path. It also tells you what the core abstraction is: steps, policy rules, form schemas or integration orchestration.
- Who will write the configuration? It sounds like staffing but it is architecture. Internal engineers can handle expressive power; customer admins and support staff need guardrails such as validation, previews, narrow building blocks and safe defaults. The answer reshapes the UX, the validation model and the authorisation model.
- Which integrations and extensions can't be dropped? Typical items are webhook callbacks, outbound notifications, CRM (customer relationship management) sync, document generation, ticket creation and auth hooks. The list fixes the adapter interface, settles sync versus async, and decides whether a failed call blocks a transition or degrades gracefully.
- What upgrade promise do customers expect? If configs must outlive core releases, you need versioning, migration tooling, backward-compatible schema changes and rollback paths. Without them the "product core" is really a services project with no end.
- Where does the security boundary for custom code sit? Options are the trusted core itself, a sandbox, or approved integration points only. The choice sets tenant isolation and supply-chain scrutiny. Default to a declarative core, with arbitrary code kept beyond the main trust boundary.
- How fast must a configuration change go live? Changes needed within hours call for self-service tooling with validation built in. A week allows more manual review. The target decides where you invest: admin UX, reusable templates, automated testing or partner onboarding.
🎯 Interview Pointer: Expect follow-ups on question 5, the custom-code boundary. Have the default ready (a declarative core, with any arbitrary code kept outside the trust boundary) and link it to the sandbox component in the architecture section and the controls in the security section.
Must-Have Functional Requirements
- A stable workflow engine that runs one core lifecycle for all customers.
- Declarative configuration (schemas, rules, UI metadata) for labels, fields, visibility and validation, so no customer needs a code fork.
- An adapter interface, so external systems plug in and the engine stays untouched.
- Versioned configuration with validation, so every change can be reviewed, tested and reverted.
- A small set of controlled extension points for the rare cases declarative configuration cannot cover.
- Migration and rollback tooling, so a version change never strands a customer on an incompatible config.
- How they fit: the engine is the centre, so there is one release train, one way to operate and one thing to support. Most variation lives in declarative configuration, not code. Integrations go through the adapter interface instead of each becoming a special case. Versioning and validation make customisation supportable. Extension points stay narrow and explicit, never a blank cheque.
Measurable Non-Functional Requirements
- One core release train: a single product ships, never ten customer branches.
- Configuration isolation: no customer's settings, data or test changes can leak into how another customer's workflows run.
- Backward-compatible upgrades: an existing configuration keeps working until a planned migration runs.
- Observable customer-specific behaviour: when a workflow goes wrong, operators can see the config version involved and which rule or adapter fired.
- These shape the system; they are not wishes. No observability means no support. No backward compatibility turns configuration into a liability. Weak isolation turns customisation into a steady stream of multi-tenant incidents.
MVP Non-Goals / Scope Fence
- The MVP (minimum viable product) leaves out:
- Code written by customers and run inside the workflow engine.
- Endless per-customer UI branches to meet branding requests.
- A general-purpose rules language that tries to model any business process.
- Bespoke integration behaviour that goes around the adapter contract.
- Automatic, unreviewed migration of every legacy configuration shape.
- These cuts protect the core. The MVP proves that one platform can serve ten customers with safe variation, not that it can model every workflow on day one.
Requirement-to-Component Traceability
- Give every requirement a likely home. The table shows a clean path from requirements to components:
| Requirement | Likely component |
|---|---|
| Stable workflow engine | Workflow runtime / orchestrator |
| Declarative schemas, rules, UI metadata | Config model, schema registry, admin UI |
| Adapter interface for integrations | Integration gateway / connector layer |
| Versioned configuration and validation | Config service, validator, release pipeline |
| Controlled extension points | Sandbox or plugin boundary |
| Migration and rollback tooling | Deployment and ops tooling |
| One core release train | Shared platform build and release process |
| Configuration isolation | Tenant-aware data and authorisation layer |
| Backward-compatible upgrades | Schema evolution and compatibility checks |
| Observable customer-specific behaviour | Audit logs, metrics, tracing, config-version tags |
Handling Partial Answers
- If only some questions get answers, don't stall. Assume sensibly and guard the riskiest constraint.
- Say the customers look alike but nobody says who authors configuration. Assume a mix: the customer's admins own the safe declarative fields, and our ops team signs off any extension or schema change. That guards the most fragile line, between safe customisation and unsafe code.
- This tests discovery under ambiguity, prioritisation with gaps, and the habit of protecting delivery by narrowing scope.
The Strong Candidate Move
- The strong move is not "make everything flexible". It runs in order: find the recurring differences, settle who owns configuration, state the integration and upgrade promise, draw a firm line for custom code, then commit to a stable engine backed by declarative configuration, versioning and strict validation.
- That order turns discovery into a buildable plan, not a feature list. Ask only questions that change the design, then proceed on stated assumptions.
Tutorial 2 · 14 min — The full lesson: how to reason through this case from first question to rollout.
3. Scale Estimates, SLOs, and Capacity
Load Shape, Not Marketing Shape
- A tidy first sketch has a single workflow engine, config store, validator and deployment pipeline, all shared. It holds up at average load. Add a deadline and it falls over.
- Customer count is the wrong thing to estimate. Ask instead how many edits, validation runs, activations and approval events land together, and what happens to them as a deadline nears.
- A working assumption: 100 tenants and 50 workflow templates, producing 1,000 configuration versions per day. That traffic mixes small edits, bulk template updates and bursts around release windows.
- Spread evenly, that is ~42 versions/hour.
- If half the day's changes arrive inside a 2-hour business window, the peak is ~250 versions/hour. Retries, failed validations and resubmissions come on top.
- Size well above the average, often by several times, with a growth factor and a headroom target. Then a release-day spike does not turn configuration into an outage.
The Four-Step Capacity Envelope
- Work through the estimate in the order that moves the architecture:
- Version intake and validation throughput. Validation means schema, policy and dependency checks plus a dry run. Say they cost 2–10 seconds of CPU-bound work (my rough guess) or a few network calls. Quick user feedback must then split from slower checks that run asynchronously. The near-term SLO (service level objective): the edit saved and came back with a clear result, fast. The deeper promise: validation finished, and promoted or rejected the version, before the deployment window shut.
- Deployment throughput. Perhaps only some of the 1,000 versions/day get promoted, but each promotion fans out across tenants, regions or integrations. One template update can reach dozens of workflow instances or queue downstream sync jobs. Fan-out sets deployment capacity, not the number of human edits.
- Limits for expressive power. Hard caps on rules, custom fields and plugins pin down runtime cost early. Think of a moderate number of fields per form, a bounded count of rule clauses per transition, and short plugin runs with memory and network limits. Caps stop one customer's customisation from eating the shared budget every other tenant relies on.
- State growth and retention. Event history outgrows configuration history, yet config history still counts. Record volume is half the question. The other half is what audit, rollback and support require you to keep, and for what period. Keep the version graph small, store deltas where they pay off, and treat rollback metadata as a first-class record.
SLOs Tied to Customer Pain
- Availability: When users need to, can they open the workflow editor, validate a change and activate a version?
- Latency: How long does the editor sit waiting on a save, a validation preview or an approval routing result?
- Freshness: Once a configuration is approved, how soon does the runtime path start using it?
- Quality: What share of deployments fail validation, get rolled back, or need someone to step in by hand?
- Security: Are audit trails, plugin and approval permissions, and tenant boundaries enforced at the right control points?
- Cost: What does each validated version, activated template or thousand workflow executions cost, and how does adding rules or plugins change that?
- Split the latency budget by step instead of quoting one figure. A save should feel instant and a validation preview may lag a little. Full deployment may take longer, provided you can trust and watch it. Making every step synchronous because it seems simpler is a mistake.
Leverage and What Belongs in Core
- Leverage tells you where a capability lives: shared product code, configuration, or an adapter around one customer's integration.
$$ Leverage=\frac{Customers\ served\ by\ shared\ capability}{Engineering\ effort} $$
- High leverage earns first-class core support. An approval model that 80 tenants use is an example.
- Low leverage, such as one customer's brittle edge-case approval chain, goes into configuration, an adapter or a tightly scoped extension. Don't bend the whole platform around it.
- Count support cost as well. A feature can look reusable yet bring costly onboarding, hard debugging or painful upgrades. Its real leverage may then trail a simpler, narrower design.
A Whiteboard Estimate That Changes Component Choice
- The naive plan validates inside the API that also saves the configuration. That looks fine until you estimate peak load. Worst-case request time then tracks the slowest rule set or plugin call, and one slow tenant holds capacity everyone else needs. The control plane turns brittle.
- With peak validation throughput and deployment fan-out on the board, the design changes:
- Save → fast and durable.
- Validation → queued, bounded, observable.
- Deployment → idempotent and retryable, kept apart from authoring traffic.
- Tenant count matters less than you might think. Component choice and partitioning depend most on how often validation and deployment run, and what each run costs.
Sensitivity Table Across Growth Scenarios
I picked these volumes and peak factors for this case. They are not industry benchmarks: justify them, don't cite them.
| Scenario | Daily config versions | Peak factor | Operational implication |
|---|---|---|---|
| Baseline | 1,000 | ~6x (the 2-hour window above) | One shared validator can cope if most of the work runs asynchronously |
| Moderate growth | 10,000 | 3x–5x | Separate queues, pools of workers, tighter caps on plugin run time |
| Aggressive growth | 100,000 (100x baseline) | 5x+ | Explicit quotas per tenant, sharded queues, firmer tenant isolation |
- Growth of 10x raises more than cost; it can demand a new partitioning strategy. Custom fields and rules behave the same way. Deeply branched forms full of computed fields may call for cached execution plans, partial evaluation, or checks done at compile time. Plugins that run often may need a sandbox or an adapter boundary instead of running in-process.
🎯 Interview Pointer: Expect a follow-up on why 10x growth calls for new partitioning and not just extra servers. The reason is noisy-neighbour risk. Past a certain point, a shared validator or queue can no longer keep one misbehaving tenant's blast radius contained. Interviewers value that reasoning more than a precise spreadsheet.
Average vs. Peak: Headroom as a Product Feature
- Average load shows whether the system makes economic sense. Peak load shows whether customers can rely on it on their worst day.
- Headroom is the gap between average and peak, and it is not waste. It stops a release window, a rush of approvals or a big tenant onboarding from becoming the day the platform breaks its promise.
- Widen the unit economics question. Running cost is one part; the support load the design creates is the other. A cheap design that needs constant hands-on work can lose to a slightly dearer one that is simpler to roll back, explain and support.
Decision Rule to Carry Forward
- Every estimate should act as a design lever. Does the number move a queue, a quota, a latency budget, a support process or a component boundary? If not, it is likely trivia. If so, record it and be ready to defend it.
4. Architecture and End-to-End Flow
Control Plane vs. Data Plane
- To present this system, follow one tenant request end to end, then replay it with a dependency in poor health.
- A hidden constraint: a customer's own approval logic becomes dangerous if it can change how the shared runtime behaves.
- So the design has two halves. The control plane is where configuration gets written, checked, versioned and promoted. The data plane runs live workflow instances. Keeping them apart moves risky change management off the hot path.
- Control plane duties: editing configs, checking schemas, running tenant contract tests, promoting releases.
- Data plane duties: workflow execution, runtime UI rendering, connector calls and audit events.
- Asked where the trust boundary sits? Point at this split first.
Architecture Diagram and Trust Boundaries
- Read the diagram top to bottom. A tenant's change request enters the control plane. It passes the config editor, then the schema/rule validator and tenant test harness, then the configuration registry (the system of record), then the feature flag service that gates exposure. The data plane follows: workflow runtime, UI renderer, integration adapter SDK, and approved extensions in a sandbox. External systems sit last: customer APIs, the identity provider and notification services.
- There are two trust boundaries. One lies between content a tenant writes and execution the platform owns. The other lies between platform execution and outside dependencies.
- Config written by a tenant never runs as code with full platform trust.
- Even approved extensions stay untrusted enough to need isolation and quotas.
- You do not control external systems. Give every adapter call timeouts, retries, idempotency where it applies, and a backoff policy that does not make a failure worse.
Core Components in Dependency Order
- The components below follow their dependencies, not the order in which they come to mind.
| Component | Responsibility | State ownership | Typical boundary |
|---|---|---|---|
| Workflow runtime | Runs workflow instances, moves between steps, fires timers and retries | Execution history, runtime state | Data plane; step decisions are synchronous, long-running work is asynchronous |
| Configuration registry | Keeps every config and schema version with its release metadata | Immutable config versions | Control plane; the system of record once a config is published |
| Schema and rule validator | Verifies syntax, semantic rules, permissions and compatibility | Only its check results | Control plane; blocks on save/publish |
| UI renderer | Turns approved config into each tenant's forms, labels and layouts | Cache only | Mostly data plane; renders synchronously from cached descriptors |
| Integration adapter SDK | A common contract for webhook, ticketing, ERP and CRM adapters | Adapter definitions, credentials references | Data plane edge facing external systems |
| Feature flag service | Controls rollout per tenant, cohort or percentage | Flag state | Spans both planes |
| Sandbox for approved extensions | Executes tightly scoped customer logic or plugins under hard limits | Ephemeral execution only | Isolated; synchronous when small, async otherwise |
| Migration service | Upgrades older configs to new schema versions, or moves instances forward | Migration jobs, compatibility mappings | Control plane; mostly asynchronous |
| Tenant test harness | Re-runs each tenant's contract tests ahead of release | Test fixtures, expected outcomes | Control plane; a synchronous approval gate or an async batch |
- Each box earns its place. The validator keeps junk out of the registry. The flag service exists because publishing a version and showing it to every tenant are two different actions. The sandbox exists because approved extensions are valuable, yet letting arbitrary code run at runtime is how customisation ends in incident response.
Failure-Path Overlay: Infinite Approval Loop
- Here the architecture is marked up for the infinite-approval-loop failure drill, so the blast radius is visible. The validator, the test harness and the flags each shrink it at a separate stage. If one layer misses the defect, a later one still prevents a tenant-wide incident.
Happy-Path Sequence: Author to Promotion
- The live release path, one step at a time:
- Author config against a versioned schema. The workflow definition, approval graph, field layout and adapter mappings are edited by a customer admin, an implementation engineer or a product manager.
- Validate syntax and semantic constraints. Checks cover structure, permitted field types, whether the approval graph makes sense, reference integrity, permissions, and fit with the features switched on.
- Run tenant contract tests. It replays typical scenarios: form submission, approval routing, odd edge inputs and integration stubs.
- Publish an immutable version. The registry keeps that precise artifact plus its version tag, schema version, release metadata and who wrote it, so it records exactly what was approved.
- Roll out behind a tenant flag. One tenant sees it first, then a cohort, then wider traffic once it proves stable.
- Observe behaviour. Watch step latency, failed validations, adapter errors, approval-loop detection and how often users finish the workflow.
- Promote or rollback. A release that clears the acceptance gate goes active. A release that fails has its flag switched back, or its tenant stays pinned to the earlier immutable version.
- The subtle point: publishing a version is not switching it on everywhere. Keeping those apart stops one bad config from causing a wide outage.
- Some steps should block and some should not. Validation and contract tests are synchronous gates, because they must hold back promotion. Bulk replay, migration and some integration recovery suit async work, so the user's request does not stay open. The runtime usually does both: quick step decisions inline, and external side effects pushed to async work when a dependency is slow or flaky.
Systems of Record, Caches, Queues, and Backpressure
- System of record: published versions belong to the configuration registry; execution history normally belongs to the workflow audit store.
- Caches: recent flag decisions, adapter metadata, form layouts once rendered, and compiled schema descriptors.
- Queues: test harness batches, migration tasks, event delivery, retries and adapter jobs.
- Backpressure and flow control: as adapter queues grow, the runtime should drop or defer noncritical work, not let retries stampede external systems.
- The partitioning key is normally tenant ID, sometimes paired with workflow definition ID or environment. It stops a noisy tenant from spoiling latency for the rest, makes replay easier, and gives quotas an obvious place to be enforced. A surge of approvals from one tenant should stay inside its partition.
MVP vs. Later Evolution
- For the MVP (minimum viable product), stay ruthlessly simple: the runtime, a versioned registry, the validator, feature flags, the tenant test harness, a small adapter SDK, and only as much sandbox as approved extensions strictly need. Prefer immutable config versions and an obvious rollback over a fancier live-editing system.
- Leave for later: config templates shared across tenants, more automated migration, a wider plugin marketplace and deeper visibility into tenant behaviour. Add them once the core release path is stable and easy to support.
90-Second Interview Summary
- "I'd split the system into two planes. The control plane is where people write configs, where schemas and rules get checked, where tenant contract tests run, and where versions are frozen and rolled out with feature flags. The data plane runs workflows, renders the UI and calls adapters. Published versions live in the configuration registry, which is the system of record, and the runtime only reads approved releases. I'd partition mainly by tenant ID, with queues and backpressure shielding external dependencies. The hardest trade-off is how far customer logic may go inside approved extensions, versus keeping it all as declarative config. For the MVP I'd keep extensions narrow, insist on versioned configs, and send every release through a tenant test harness and a staged flag rollout. My first production gate is one tenant, with a rollback I can run straight away."
Tutorial 3 · 10 min — The full lesson: how to reason through this case from first question to rollout.
5. Data Model, APIs, and Working Code
Four Core Records and Ownership
- Abstractions do not convince an interviewer. Concrete state does. Tie the riskiest parts of the design to records, contracts and a little code. Say what is durable and what can change. Say what carries a version, what gets deployed, and what the system does when an identical request arrives a second time.
WorkflowTemplate(id, engine_version, schema)— the shared blueprint. Product and platform own it.idis the primary key.engine_versionnames the evaluator and renderer that can read this template.schemasets the permitted shape of fields, approvals and integrations.- Lifecycle:
draft→published. Once published, the artifact never changes; new behaviour needs a new template version. - Retention: hold published templates for as long as the product or contract window lasts. Older tenant configs must still resolve against the precise template they were validated with. Drafts can be garbage-collected once they have sat idle for a safe period.
TenantConfig(tenant_id, template_id, version, values)— the overlay one customer applies on top of a template.- In practice the key is
(tenant_id, version).valuescarries only the tenant parameters that the template schema permits. - Lifecycle:
draft→validated→deployedorrolled_back. When validation fails, the record goes back todraft/needs_fixand keeps its version number. - Retention: every published and rolled-back version stays, so audit and rollback both work. Drafts follow platform policy and are dropped once abandoned or superseded.
- What the values mean for the business is the tenant's call. Validating and storing them is the platform's job.
- In practice the key is
AdapterDefinition(name, contract_version, permissions)— the contract for talking to one downstream system, such as CRM (customer relationship management), ticketing, storage or messaging.namepluscontract_versionform the key.permissionslists the scopes and capabilities the adapter needs, so the platform can turn down a config that requests an adapter it has no safe way to call.- Lifecycle: the platform or integrations team runs it. Contracts ship as versions and retire only when no active tenant config still relies on them.
- Retention: retired definitions stay around for past deployments and debugging. They are flagged inactive, so a new config cannot pick them.
ConfigDeployment(tenant_id, version, state)— the deployment ledger. It alone decides which ofdraft/validated/deployed/rolled_back/failedapplies.- Ops and support read this record to see which version is live at this moment.
- Lifecycle: each state change is appended; nothing is rewritten in place. A rollback adds a fresh deployment event that points at an earlier good version.
- Retention: hold the whole history for the full support and audit window. Incident reviews, customer support and change tracking all depend on it.
- Separate owners stop the records from tangling. Product owns the template. The customer owns the tenant config. Integrations owns the adapter contract. Ops owns the deployment record. Once those lines blur, you get forks that call themselves flexibility.
🎯 Interview Pointer: If asked what tells you which version is live, answer
ConfigDeployment, notTenantConfig. Its state transitions are append-only. That ledger, not the config record, lets support see what is running now, and it turns a rollback into a new event rather than an edit in place.
Four API Contracts
- At interview scale, show one bounded declarative contract plus a write path that stops unsafe input before the registry ever sees it. The point is to prove three things: the platform takes limited configuration, checks it, and publishes it as an immutable version.
POST /v1/configurations/validate— takes a draft tenant config and hands back validation errors plus a normalised preview. It never publishes anything.- Auth: a bearer token or session scoped to the caller's tenant. Authorisation permits validation only for tenants the caller is allowed to administer.
- Body:
template_id,version,values, and an optionalidempotency_keyso validation traces can be replayed. The call has no write effects, so retrying it is safe anyway. - Responses:
200 OKfor a good preview;400 Bad Requestwhen the structure is wrong;403 Forbiddenfor a tenant mismatch or missing rights;422 Unprocessable Contentwhen schema or policy rules fail.
POST /v1/tenants/{id}/deployments— starts a deployment for a tenant, usually from a config version that already passed validation. It returns the deployment ID and its state.- Auth: the caller must belong to tenant
{id}or hold a delegated ops role. The server refuses any attempt to deploy into another tenant. - Because this call creates something, it needs an idempotency key. The same key with an identical body gives back the same result. The same key with a different body gets
422 Unprocessable Content, as the IETF Idempotency-Key draft recommends. If a retry arrives while the first request is still in progress, it gets409 Conflict. - Errors:
401 Unauthorizedwhen credentials are absent or expired;403 Forbiddenwhen tenant permission is missing;404 Not Foundwhen the named config version does not exist;409 Conflictfor a stale optimistic-concurrency token or a key that is still in flight;422 Unprocessable Contentwhen syntax passes but policy fails, or when a key is reused with a different payload.
- Auth: the caller must belong to tenant
POST /v1/adapters/{name}/test— runs an adapter mapping against a harmless test payload or a sandbox endpoint.- Auth: limited to integration maintainers or tenant admins.
- Body: the adapter name, a test payload, the mapping and an optional sandbox selector. An idempotency key is accepted too, so a retry does not produce a second test run or a second audit entry.
- Errors:
404 Not Foundfor an unknown adapter;403 Forbiddenwhen permission is lacking;422 Unprocessable Contentwhen the mapping cannot be applied;502 Bad Gateway, or something like it, when the sandbox dependency fails.
POST /v1/deployments/{id}/rollback— records a new rollback state for that tenant. The old deployment is left untouched.- Auth: the caller needs tenant scope and either raised ops rights or an explicit sign-off route.
- Body: identifies the target deployment or the earlier good version. It carries an idempotency key, because rollback writes state and may be retried after a failure.
- On success the caller gets the new deployment record. The previous record is never overwritten.
- Errors:
404 Not Foundwhen no deployment has that ID;409 Conflictwhen the live state has changed so much that rolling back is unsafe;422 Unprocessable Contentwhen the target version no longer fits the current template or adapter contract.
- Rules shared by all four: authentication and authorisation are tenant-aware, and writes across tenants are refused. Create-style calls demand an idempotency key. A repeat with the same key and body returns the same answer. A reused key with a new body is rejected; it never becomes a quiet update.
Idempotent Deployment Creation Walkthrough
- Worked case: an ops client calls
POST /v1/tenants/acme/deployments. Its body is{configVersion: 17, templateId: "tpl-9"}; its key isdeploy-2024-11-18-001.- The server makes deployment
dep-555and replies201 Createdwithdeployment_id=dep-555andstate=deployed. The write log stores the idempotency key alongside. - The client times out and retries with that key. No
dep-556may appear. The reply is identical and still refers todep-555. - The client then retries with that key but sends
configVersion: 18. The payload no longer matches what the key was first used for, so the server answers422.
- The server makes deployment
The Smallest Safe Code Path: Config Publication
- Config publication carries the most risk. Bad structure, broken mappings or faulty versioning at this step can take production down. The sketch below is small, yet it contains every control point that counts in a live system.
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
type JsonLogic = { op: string; args?: JsonValue[] };
type WorkflowConfig = {
version: 1;
fields: Array<{ key: string; type: "text" | "number" | "date"; required: boolean }>;
approvals: Array<{ when: JsonLogic; role: string; slaHours: number }>;
integrations: Array<{ adapter: string; mapping: Record<string, string> }>;
};
type ValidationError = { path: string; code: string; message: string };
type PublishResult =
| { ok: true; configId: string; version: number }
| { ok: false; errors: ValidationError[] };
class ConfigValidationError extends Error {
constructor(public errors: ValidationError[]) {
super("Configuration validation failed");
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function validateWorkflowConfig(input: unknown): WorkflowConfig {
const errors: ValidationError[] = [];
if (!isPlainObject(input)) {
throw new ConfigValidationError([{ path: "$", code: "invalid_type", message: "Expected an object" }]);
}
if (input.version !== 1) {
errors.push({ path: "version", code: "unsupported_version", message: "Only version 1 is supported" });
}
if (!Array.isArray(input.fields) || input.fields.length === 0) {
errors.push({ path: "fields", code: "required", message: "At least one field is required" });
}
if (!Array.isArray(input.approvals)) {
errors.push({ path: "approvals", code: "invalid_type", message: "Approvals must be an array" });
}
if (!Array.isArray(input.integrations)) {
errors.push({ path: "integrations", code: "invalid_type", message: "Integrations must be an array" });
}
if (errors.length > 0) {
throw new ConfigValidationError(errors);
}
return input as WorkflowConfig;
}
function validatePolicy(config: WorkflowConfig): void {
const seenRoles = new Set<string>();
for (const approval of config.approvals) {
if (!approval.role || approval.slaHours <= 0) {
throw new ConfigValidationError([
{ path: "approvals", code: "invalid_approval", message: "Each approval must have a role and positive SLA" },
]);
}
if (seenRoles.has(approval.role)) {
throw new ConfigValidationError([
{ path: "approvals", code: "duplicate_role", message: `Duplicate approval role: ${approval.role}` },
]);
}
seenRoles.add(approval.role);
}
}
function validateAdapterMappings(config: WorkflowConfig, allowedAdapters: Set<string>): void {
for (const integration of config.integrations) {
if (!allowedAdapters.has(integration.adapter)) {
throw new ConfigValidationError([
{ path: "integrations", code: "unknown_adapter", message: `Unknown adapter: ${integration.adapter}` },
]);
}
for (const [from, to] of Object.entries(integration.mapping)) {
if (!from || !to) {
throw new ConfigValidationError([
{ path: `integrations.${integration.adapter}.mapping`, code: "invalid_mapping", message: "Mappings must not contain empty keys" },
]);
}
}
}
}
class Registry {
private store = new Map<string, { version: number; config: WorkflowConfig; hash: string }>();
putImmutable(configId: string, config: WorkflowConfig): { version: number } {
const existing = this.store.get(configId);
const nextVersion = existing ? existing.version + 1 : 1;
const hash = JSON.stringify(config);
this.store.set(configId, { version: nextVersion, config, hash });
return { version: nextVersion };
}
}
function publish(configId: string, raw: unknown, allowedAdapters: Set<string>, registry: Registry): PublishResult {
try {
const config = validateWorkflowConfig(raw);
validatePolicy(config);
validateAdapterMappings(config, allowedAdapters);
const result = registry.putImmutable(configId, config);
return { ok: true, configId, version: result.version };
} catch (error) {
if (error instanceof ConfigValidationError) {
return { ok: false, errors: error.errors };
}
return {
ok: false,
errors: [{ path: "$", code: "internal_error", message: "Unexpected failure during publish" }],
};
}
}
- What each part teaches:
WorkflowConfiglimits the shape to a declarative contract with a version.JsonValue/JsonLogicstate approval conditions openly and leave no route for arbitrary code.ValidationErrorhands the API a fixed set of error terms.validateWorkflowConfigvalidates types at the boundary. Malformed payloads stop there, before any business logic runs.validatePolicykeeps authorisation and workflow rules apart from structural checks.validateAdapterMappingscompares the integration surface with an allowlist instead of trusting whoever wrote the config.Registry.putImmutablestores each write as a new version; nothing is overwritten.publishruns the checks in the one order that holds up: parse → validate → authorise-by-policy → persist.
🎯 Interview Pointer: Commit the sequence to memory and reuse it elsewhere: parse → validate → authorise-by-policy → persist. Expect the follow-up "why that order?" Cheap structural checks come first and fail fast, so the costly policy and adapter checks only run on sound input. Nothing is written until every earlier step passes, so the path fails closed.
What the Whiteboard Version Omits on Purpose
- A production service would also need:
- Optimistic concurrency — each write should state the template or config version it expects. Then two operators cannot race and silently overwrite each other. If the version travels in the body, a mismatch returns
409 Conflict. If it travels in anIf-Matchheader, HTTP calls for412 Precondition Failed. - Idempotency — retrying after a timeout must never yield a second deployment or a second version.
- Bounded, safe retries — retry only failure types that are safe to repeat. A rejected validation or authorisation is not transient, so never replay it as if it were.
- Observability — each validation, deployment, adapter test and rollback should carry the request ID, the tenant ID, the idempotency key and the config version. Support can then rebuild exactly what happened without combing through application logs.
- Optimistic concurrency — each write should state the template or config version it expects. Then two operators cannot race and silently overwrite each other. If the version travels in the body, a mismatch returns
Contract and Failure-Injection Tests
- A strong answer brings two tests. One pins the contract; the other injects a failure.
import { describe, it, expect } from "vitest";
describe("publish", () => {
it("rejects duplicate approval roles and returns a structured validation error", () => {
const registry = new Registry();
const result = publish(
"cfg-1",
{
version: 1,
fields: [{ key: "requester", type: "text", required: true }],
approvals: [
{ when: { op: "always" }, role: "manager", slaHours: 24 },
{ when: { op: "always" }, role: "manager", slaHours: 24 },
],
integrations: [],
},
new Set(["crm"]),
registry,
);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors[0].code).toBe("duplicate_role");
}
});
it("survives an adapter allowlist miss without publishing state", () => {
const registry = new Registry();
const result = publish(
"cfg-2",
{
version: 1,
fields: [{ key: "requester", type: "text", required: true }],
approvals: [{ when: { op: "always" }, role: "manager", slaHours: 24 }],
integrations: [{ adapter: "unknown", mapping: { a: "b" } }],
},
new Set(["crm"]),
registry,
);
expect(result.ok).toBe(false);
});
});
- The first, the contract test, shows the API turning away a config whose structure is fine but which breaks policy.
- The second, for failure injection, feeds in an unsafe adapter definition and confirms that publishing fails closed.
- A real service would add more: tests for duplicate idempotency keys, tests for deployment rollback, and a concurrency test proving that stale versions are refused.
- Why this matters for hiring: teams need FDEs (Forward Deployed Engineers) who can go from a customer's workflow pain to a state model, then to API semantics, then to validation code fit for production. That is the gap between sketching a system and shipping a platform others can support.
Tutorial 4 · 10 min — The full lesson: how to reason through this case from first question to rollout.
6. Security, Reliability, and Failure Handling
The Red-Team Question
- The security and operations review decides whether this platform ships or stays a good demo. Assume the hardest realistic request: deep workflow control for customers, a core that stays stable anyway, secrets kept per tenant, and one customer's broken config that never becomes everyone's outage.
- A sharp reviewer asks: "A config goes live and its approvals loop forever. Then what?" The test is less about correct logic and more about whether the platform limits the damage, keeps the evidence and keeps serving every other tenant.
- Here is the concrete answer. Before activation, the config pipeline turns away any cycle it can see. Some loops only appear after publish, through hidden state or a race. When that happens, the engine stops that workflow version and marks the bad revision inactive. Customers can still use the version that last worked. For audit and replay, the system logs who published, when, for which tenant, plus a snapshot of the rule graph. Loops will happen. The aim is that each one is dull, bounded and traced to a named change.
Four Security Controls
- Disallow arbitrary code by default. Customers shape workflows through declarations: fields, validation rules, routing, approvals and adapter bindings. Custom code is a last resort; if ever allowed, it runs in a tightly limited sandbox or behind a separate, approval-gated extension path.
- Scope adapters and secrets per tenant. Compartmentalise every integration so no tenant sees a neighbour's credentials, tokens or request bodies. Apply least privilege in two places: the runtime that carries out actions, and the control plane that publishes config. A support engineer allowed to read a tenant's config does not, by that fact, get to call that tenant's external systems.
- Validate rules for denial-of-service risk. Rich validation can turn into a way to exhaust resources. Deeply nested conditions, unbounded regexes, huge lookup tables and recursive approval paths can each burn CPU or memory without limit. So the publish path checks shape, depth and size against fixed caps, and runs cycle detection, before any config goes live.
- Audit configuration publishers and versions. Each change leaves a lasting record: the publisher, the diff, the review or approval gate it passed, and the version it replaced. It is not paperwork. With it, operations can find the revision that broke things, and security can confirm the change was authorised.
- In the interview, pair each control with the failure it stops: running code, leaking secrets, abusing resources and losing track of who changed what.
Failure-Policy Decision Table
| Event | Default behaviour | Why |
|---|---|---|
| Checks at publish cannot show the workflow is safe to switch on | Fail closed | Blocking a doubtful publish costs less than running an unsafe revision for a tenant |
| An external adapter hits its timeout mid-run | Bounded retries, then a queue or the dead-letter path | A brief outage should not fail the customer's work on the spot |
| The approval graph contains a cycle, or starts looping while running | Fail closed for that workflow version | The loop will do the same harm every time; letting it run only adds more |
| An integration further down is offline, yet the task is not urgent | Degrade or queue | Hold the work rather than lose it, as long as the SLA tolerates the wait |
| The customer's action cannot be undone, for example a side effect in another system | When in doubt, hand it to a human | When the system has no safe way to undo, it must not guess |
| Someone asks for custom code that looks suspicious | Deny by default | Execution rights need a clear trust boundary before the platform hands them out |
- Lead with this table. It shows failure handling was designed up front, not improvised on the night.
Blast-Radius Containment Levels
- Tenant blast radius: a broken config from one customer must leave every other customer's workflow state, secrets and run queue untouched.
- Region blast radius: in a multi-region setup, keep a faulty deploy or a failing dependency inside the smallest region slice you can.
- Workflow blast radius: freeze just the workflow definition or version at fault. The rest of the tenant keeps running, unless its control plane itself is compromised.
- Dependency blast radius: when one adapter fails, fence it off. Other adapters carry on as normal.
- Layer the defences. Validation is the first layer, the runtime sandbox the second and rate limits the third. Observability comes last and shows that the other three still hold.
Failure Drill: Infinite Approval Loop
- The setup: a tenant publishes a workflow in which step A sends work to manager approval. Manager approval hands off to compliance approval. Compliance approval then sends it back to the manager, whenever a condition holds, and that condition never turns false.
- A good answer covers four stages:
- Detection — at publish time, static analysis finds the cycle where it can. A loop that only shows itself at runtime is caught by the engine, which spots the same state transitions repeating and trips a max-hop or max-revisit guard.
- Containment — freeze that workflow version. Stop only the execution group it affects, and block any new run from starting on that revision.
- Recovery — fall back to the last known-good version. If the workflow has already caused side effects, queue the affected requests for an operator to review.
- Prevention — tighten graph validation and add test cases for cyclic approvals. Adopt a publish-time policy that refuses any config whose approval graph has no topological order.
- Keep the evidence: the exact config revision, the trace of transitions and the history of operator actions. An error message alone does not support a forensic review; this record does.
Other Named Failure Cases
- Adapter contract changes. A service downstream renames a field the platform relies on, or changes the shape of its response. To mitigate, version the adapters, pin the schema each expects, add compatibility checks, and quarantine any mismatch rather than letting it pass as a quiet partial success.
- Tenant upgrade breaks old field mapping. After a tenant upgrades from version 1 to version 2, integrations built earlier may still rely on the mapping it replaced. To mitigate, support versioned field maps and validate each migration. Keep the old mapping running until every execution that uses it has drained.
- Feature-flag combination is untested. Two toggles can each be safe alone and still create a bad path together. To mitigate, keep a matrix of combinations known to be safe for high-risk flags. Ban unsupported combinations in production. At launch, any combination nobody has reviewed fails closed.
- Custom code escapes sandbox. Once customer-defined logic runs anywhere, a sandbox breakout counts as a high-severity event. To mitigate, leave arbitrary code switched off by default. Where a sandbox does exist, give it least privilege, resource caps, limits on outbound network traffic and its own approval gates. A breakout should shut the extension path and start incident response.
Six Operational Primitives
- Timeouts: give each adapter its own explicit timeout, so a single hung dependency cannot stall the full workflow.
- Retries: retry only operations that are transient and idempotent. Cap the attempts and back off between them.
- Idempotency: any write that the outside world can see carries an idempotency key, or some other way to dedupe, so a retry never repeats a side effect.
- Circuit breakers: when a dependency is plainly sick, open the breaker. Traffic is shed at once rather than piling up as requests that are bound to fail.
- Dead-letter handling: once automation has used up its safe retries, send the repeat failures to a dead-letter queue or a failure inbox.
- Human escalation: a person must step in when the action cannot be undone, when the config is unclear, or when the evidence points to a policy breach and not a passing outage.
- The point is not just handling errors: at each failure, the system picks the least dangerous next state.
Evidence, Runbooks, and Launch Gates
- Have audit evidence and runbooks ready before launch. They cover who may publish config, how versions get approved, how to switch off a bad workflow, how to rotate adapters, how to revoke secrets, and how to replay one tenant's incident without exposing another tenant's data.
- A team that cannot write these answers down in calm conditions will not find them in the middle of an outage.
Interview-Sized Production Sketch
- The sketch below is deliberately narrow. It shows one publish-path invariant: a config that names an unknown adapter is refused before it can go live. I chose its caps myself as stand-ins, not as platform standards: 50 fields, 10 approvals, 20 integrations and a 168-hour SLA. A real companion repository would also need tested dependencies, fuller schema validation, structured logging and a link to a real persistence layer.
export type WorkflowConfig = {
tenantId: string;
version: number;
fields: Array<{ key: string; type: string; required?: boolean }>;
approvals: Array<{ when: Record<string, unknown>; role: string; slaHours?: number }>;
integrations: Array<{ adapter: string; mapping: Record<string, string> }>;
};
export type AdapterRegistry = {
has(adapterName: string): boolean;
};
export type PublishResult =
| { ok: true; activatedVersion: number }
| { ok: false; error: string };
const ALLOWED_FIELD_TYPES = new Set(["text", "number", "date", "boolean", "email"]);
const MAX_FIELDS = 50;
const MAX_APPROVALS = 10;
const MAX_INTEGRATIONS = 20;
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function validateConfig(config: WorkflowConfig, registry: AdapterRegistry): string | null {
if (!config.tenantId || typeof config.tenantId !== "string") return "invalid tenantId";
if (!Number.isInteger(config.version) || config.version < 1) return "invalid version";
if (!Array.isArray(config.fields) || config.fields.length > MAX_FIELDS) return "invalid fields";
if (!Array.isArray(config.approvals) || config.approvals.length > MAX_APPROVALS) return "invalid approvals";
if (!Array.isArray(config.integrations) || config.integrations.length > MAX_INTEGRATIONS) return "invalid integrations";
const seenFields = new Set<string>();
for (const field of config.fields) {
if (!field || typeof field.key !== "string" || !field.key.trim()) return "invalid field key";
if (seenFields.has(field.key)) return "duplicate field key";
seenFields.add(field.key);
if (!ALLOWED_FIELD_TYPES.has(field.type)) return `unsupported field type: ${field.type}`;
}
for (const approval of config.approvals) {
if (!approval || typeof approval.role !== "string" || !approval.role.trim()) return "invalid approval role";
if (!isPlainObject(approval.when)) return "invalid approval condition";
if (approval.slaHours !== undefined && (!Number.isInteger(approval.slaHours) || approval.slaHours < 1 || approval.slaHours > 168)) {
return "invalid approval slaHours";
}
}
for (const integration of config.integrations) {
if (!integration || typeof integration.adapter !== "string" || !integration.adapter.trim()) return "invalid adapter name";
if (!registry.has(integration.adapter)) return `unknown adapter: ${integration.adapter}`;
if (!isPlainObject(integration.mapping)) return "invalid adapter mapping";
}
return null;
}
export function publish(config: WorkflowConfig, registry: AdapterRegistry): PublishResult {
const error = validateConfig(config, registry);
if (error) {
return { ok: false, error };
}
return { ok: true, activatedVersion: config.version };
}
export function configWithAdapter(adapterName: string): WorkflowConfig {
return {
tenantId: "tenant-acme",
version: 1,
fields: [{ key: "requester", type: "text", required: true }],
approvals: [{ when: { op: "always" }, role: "manager", slaHours: 24 }],
integrations: [{ adapter: adapterName, mapping: { a: "b" } }],
};
}
// Example failure test.
describe("publish", () => {
it("rejects an unknown adapter", () => {
const registry: AdapterRegistry = { has: (adapterName: string) => adapterName === "crm" };
expect(() => {
const result = publish(configWithAdapter("arbitrary-shell"), registry);
if (result.ok) throw new Error("expected publish to fail");
throw new Error(result.error);
}).toThrow(/unknown adapter/);
});
it("accepts a known adapter", () => {
const registry: AdapterRegistry = { has: (adapterName: string) => adapterName === "crm" };
const result = publish(configWithAdapter("crm"), registry);
expect(result.ok).toBe(true);
});
});
- The test does not claim the code is finished. What it does is make the invariant plain. If a config names an adapter the platform has never heard of, the tenant cannot activate it, and publish fails closed long before runtime.
What to Say in the Interview
- Show your judgement out loud. Arbitrary code is off unless explicitly allowed. Each tenant's secrets and adapters live in their own compartment. Risky rules are checked before they go live. Every publish leaves an audit trail. Each external dependency and each irreversible action has a named failure policy. Then link all of this to supportability: with evidence in hand, the platform can degrade, queue or block, and it never has to guess.
- Hiring teams want someone who owns safe rollout, support and incident response beyond the happy path, and who guards the product core while customers still feel their workflows bend to fit them.
- The lesson to carry away: give every external dependency and every irreversible action an explicit policy for failure and recovery. State that policy at four levels (tenant, region, workflow and dependency), and back it with a publish-time invariant so unsafe config never reaches production.
Tutorial 5 · 9 min — The full lesson: how to reason through this case from first question to rollout.
7. Delivery Plan, Observability, and Business Impact
Rollout as a Series of Proofs
- When the prototype works, the customer stops asking whether the platform can do the job. They start asking whether they can trust it in production, with their own data, their own approval chains and their own support staff. Here the FDE's role shifts. The clever demo is done; the work now is to turn the architecture into staged delivery, with a measurable gate at every stage.
- Do not plan one big go-live. Plan a sequence of proofs:
- Start with a workflow that several customers already share. Pick its least contested form: a single approval path, a single integration, a single field set and a single brand surface. The aim is depth, not breadth. Show that a real customer need fits the platform with no fork.
- Next, bring on one willing customer who copes with loose ends and gives feedback quickly. Name an owner on your side, keep a single production gate, and know the rollback path before the first publish. Release as a canary first, to a thin slice of traffic or to one low-risk group of tenants. The opening launch stays tiny on purpose: support should be able to trace each event, each rejected config and each request sent to a downstream system.
- Widen the configuration surface only when evidence keeps showing three things: the current surface meets real demand, it cuts duplicated work, and it adds no incident risk. That discipline is what separates a platform from a growing pile of tenant-specific debt.
The Four-Phase Rollout Plan
- Extract one recurring workflow.
- Owner: a product engineer, with the FDE in support.
- Exit criteria: shared primitives can express the workflow, so no one-off fork is needed.
- Go/no-go gate: the team judges the pattern settled enough to build into the platform.
- Onboard a willing customer.
- Owner: the FDE together with the customer admin.
- Exit criteria: the customer configures the workflow, finishes a pilot run and raises issues through the support channel.
- Go/no-go gate: the customer signs up to the support model and the rollback procedure.
- Measure delivery time and defects.
- Owner: the platform lead for telemetry, and the support lead for classifying incidents.
- Exit criteria: starting figures exist for how fast tenants launch, how many defects appear and how often validation fails, all captured before any scope is added.
- Go/no-go gate: the data shows configuration is cutting custom work, not hiding rework somewhere else.
- Expand configuration only after repeated evidence.
- Owner: product and platform, deciding together.
- Exit criteria: a second and a third customer reuse the same mechanism, and the effort is predictable.
- Go/no-go gate: a new config type is approved only if it reduces forks or support load without adding upgrade risk.
🎯 Interview Pointer: Interviewers tend to push on the gates between phases, so know all four by heart. The pattern is stable. The customer accepts the support and rollback model. The evidence shows config cuts custom work. Customers two and three reuse it with no fresh build. These gates are what stop a rollout from becoming "we shipped because the demo went well."
The Six-Metric Scorecard
- Time to launch customer: the time from an approved request to a tenant's first successful production workflow. Data from: release tracking and ticket timestamps. Owner: delivery lead. Alert when: lead time creeps up across several customers, a sign of brittle config, slow approvals or manual steps.
- Percentage handled by configuration: of all customer-specific changes, the share delivered as config rather than code. Data from: tags on releases and change requests. Owner: product or platform lead. Alert when: the share stops rising, which usually means the team is quietly going back to forks.
- Config validation failure rate: failed publishes divided by all publish attempts. Data from: config service logs. Owner: platform engineering. Alert when: the rate climbs, which points to a confusing UX, a weak schema or unsafe requests.
- Fork count: how many customer-specific code branches, conditionals or exceptions exist that cannot be removed. Data from: architecture reviews and repository analysis. Owner: engineering manager. Alert when: the count grows, since every fork erodes the stable core.
- Upgrade time: the effort to move one customer or tenant onto a new platform version. Data from: release records and migration tickets. Owner: release manager. Alert when: an upgrade needs one-off scripts or a long freeze window.
- Tenant incident rate: a rolling count of each tenant's incidents, kept separate from outages that hit the whole platform. Data from: the incident management system. Owner: support and SRE (site reliability engineering) together. Alert when: incidents bunch around one config type or one adapter.
Four Layers of Metrics
- Technical health: how often publishes succeed, validation failures, how often rollbacks happen, latency and incident rate.
- Model/config quality: do the workflow primitives fit how customers really work? The usual clue is how often customers ask for exceptions.
- Adoption: customer count on the configured workflow, how frequently each one returns, and the share of each customer's process it now handles.
- Business outcome: faster launches, less custom engineering, fewer support escalations and quicker growth into new tenants.
- Keep the layers apart in an interview. A platform can pass every technical check and still fail commercially because nobody adopts it. It can also be widely adopted and still fail in operation because support cannot keep it stable.
Dashboard in the Language of the User
- Lead with the customer's story, then drill down into the machinery. A headline might read, with illustrative numbers: "Onboarding for this customer took under two days. It needed zero manual config edits and one validation retry, and there was no incident after launch."
- Under that headline sit the internal signals, broken out by tenant: rollout status, publish latency, adapter errors, blocked approval loops and failed schema validations.
- This is how to answer "what do we watch after launch?". CPU, logs and queue depth are not enough. The dashboard should link what the customer sees as success to the failure modes inside the system.
Ownership Before Launch
- Product owner: picks the first repeated workflow to turn into a platformised path.
- Platform owner: looks after the shared core, the config schema and the publish service.
- FDE: turns the customer's workflow into supported configuration, checks the assumptions and runs the rollout.
- Support lead: handles customer communication, triage and escalation during the pilot.
- SRE / operations lead: is responsible for monitoring, carrying out rollbacks and release health.
- Customer admin / champion: tests the workflow in their own environment and signs off that it is ready.
Go/No-Go and Rollback Triggers
- Go/no-go gate: a config may publish only when four things hold. Validation passes. The adapters it depends on are healthy. The rollback plan has been rehearsed. And the customer champion agrees that their business process and the configured workflow still line up.
- Rollback trigger: roll back on any one of these. Validation keeps failing. Integrations throw errors nobody predicted. Incidents climb among the earliest group of tenants. A new config pattern starts to make approvals unstable.
- For a stateful workflow, write the migration plan and the rollback plan separately. Moving state is a different job from reverting code.
- Ship a launch package of four documents: a short guide for admins, a runbook for support, a rollback checklist and a change log. The change log lists which parts customers may configure, which stay fixed, and which go to engineering for review.
Config vs. Adapter vs. Shared Service vs. Core
- Configuration: fields, routing choices, branding, approver lists and threshold values that differ per customer.
- Adapter: integrations that vary by customer behind one common interface, such as CRM (customer relationship management), ticketing, email, identity and document systems.
- Shared service: capabilities every tenant reuses under one uniform policy, such as validation, audit logging, workflow execution and publish orchestration.
- Core product: the building blocks the business model rests on. They stay put, and no new tenant gets to reopen them.
- When someone asks for "just one exception", test it against these four homes. Does it change the core product? Does it belong in an adapter? Can it be modelled as config? Asking this keeps the platform from sliding into per-customer branches that nobody can maintain.
The Risk Register
| Risk | Owner | Mitigation | Trigger |
|---|---|---|---|
| Infinite approval loop | Platform engineer | Detect cycles and validate at publish time | The same approval state is entered again and again |
| Adapter mismatch | Integration engineer | Contract tests plus explicit adapter versions | Calls fail after a publish, or the schema drifts |
| Customer confusion during rollout | FDE | Training, playbooks and guided setup | Support volume is high, or validation errors keep repeating |
| Unsupported fork pressure | Product manager | Clear grounds for refusing bespoke code, plus a review board for exceptions | A request cannot be expressed in configuration |
- Treat this register as part of how the platform is run, not as an appendix.
- What this shows an employer: a forward-deployed team wants people who carry a piece of work past the prototype until customers adopt it, and who then distil what customers said into a pattern the product can reuse. Call the platform finished once three things are true: customers rely on it, the workflow keeps improving, and operations can run it with no heroics needed. That is the moment "customisable" turns into "supportable".
Tutorial 6 · 11 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 result the customer wants, not the technology: "Ten customers run broadly similar workflows. What differs is their fields, their approval chains, their branding and the systems they connect to. We want one platform for all ten, with no forks. First I'd settle three questions. Which parts belong in the product core? Which parts can each tenant change safely through configuration? And which extensions must run in strict isolation? I'd lean towards a small, stable core, with tenant differences held in configuration that is versioned and tested. I'd depart from that only for a need that is genuinely one of a kind, or too dangerous to make generic."
- Those sentences carry the outcome, show architectural judgement and invite pushback. If the interviewer shifts the ground, say with a regulated sign-off step or a proprietary connector, rework the plan rather than argue for your first guess.
The 50-Minute Pacing Plan
- Let risk decide where the minutes go, not the size of the boxes. A strong candidate lingers on the part whose failure would cost the business most.
- Outcome and discovery, minutes 0–9. Minute 0: anchor on the outcome. Minute 1: the main thing the customer gets. Minute 2: which piece of the workflow moves fastest. Minute 3: surface the hidden constraint. Minute 4: what must never shift. Minute 5: the fixed requirements, namely identity, workflow semantics, auditability, rollback and support boundaries. Minutes 6 to 8: assumptions, first on tenant numbers and change rate, next on who drafts and signs off config, last on failure tolerance and incident handling. Minute 9: offer the interviewer a chance to redirect.
- Success and scale, minutes 10–14. Minute 10: success for the customer and for the team running the platform. Minute 11: what that implies for SLOs (service level objectives) and for support. Minute 12: what "safe customisation" should mean here. Minute 13: a rough load shape: tenants, workflows, connectors, admin edits. Minute 14: call every figure illustrative.
- Architecture, minutes 15–24. Minute 15: the overall design. Then one component a minute: authoring tools and their guardrails (16), validation and the gates before publish (17), immutable versions and how they roll out (18), the engine that runs workflows (19), connectors and where integrations stop (20), audit logs and observability (21), per-tenant identity and authorisation (22), reads and writes of workflow state (23). Minute 24: contrast the control path with the data path.
- Config lifecycle, minutes 25–29. Who may change a tenant's settings (25). Who checks the change (26). How it moves between environments (27). How you undo it (28). How support pins a version and reproduces a reported problem (29).
- Happy path, failure path, loop drill, minutes 30–38. Trace a good change from edit to publish (30), from publish to a running workflow (31), and from that run to its audit entry (32). Then break it: a config that fails validation (33), permissions that have drifted (34), a connector that stops responding (35), a bad publish that needs reverting (36). Minute 37 is the drill where approvals circle forever. Minute 38 shows how cycle detection and checks at publish time stop that loop before production.
- Trade-offs and follow-ups, minutes 39–46. Config against code (39). A generic engine against a domain product (40). Plugin freedom against security (41). Compatibility against simplicity (42). Then follow-ups: what belongs in the core (43), how config versions work (44), when a single-customer fork is justified (45), and how to isolate extensions (46).
- Close, minutes 47–49. Recap delivery, rollout gates, observability and support runbooks (47). Give the 90-second summary for executives (48). State the first gate for production rollout, then invite questions (49).
Four Trade-Off Pairs
- Configuration versus code. When tenants vary only in fields, labels, approvals, routing or their choice of connector, use configuration. Such differences stay visible, carry version numbers and remain easy to support. Reach for code once the variation changes what the workflow means, adds a new invariant, or would twist the config language into a programming language nobody can read. The weak line is "make everything configurable". A better one: "Put the common variations in declarative config. When a rule becomes bespoke business behaviour, promote it to reviewed, isolated code."
- Generic engine versus domain product. One generic engine can host many tenants. Make it model every business process and it turns too abstract. A domain product does less but is simpler to use and cheaper to support. The best answer combines them: a stable core built from workflow primitives the company knows it can support repeatedly, with a constrained domain model on top. Aim at the workflow family the business intends to own, not at endless extensibility.
- Plugin flexibility versus security. Plugins let teams wire in unusual customer systems without touching the core, so customers go live faster. The price is a larger attack surface. Contain it: run extensions in a sandbox, give them minimal permissions, fix their interfaces, and manage every plugin as a reviewed, versioned and observable artifact. Once a plugin can read lots of data or run any code it likes, it is no longer simple; treat it as a trusted service boundary.
- Backward compatibility versus simplification. Compatibility shields existing tenants and keeps running workflows intact. Simplification keeps the platform easy to grasp and maintain. A good candidate values both and proposes a clear deprecation policy: schemas with versions, windows for migration, tests for compatibility, and a short list of generations still supported. Promising to "support everything forever" is the weak answer; it turns support work into archaeology.
Expected Follow-Up Questions
- What becomes core product? Put in the core: the stable execution model, primitives for identity and authorisation, the state transitions every workflow goes through, audit logging, validation and the admin tools. Put in versioned configuration: per-customer labels and field definitions, the routing of approvals, and connector parameters. If something would change how workflows execute, demand custom storage behaviour, or make rollback unsafe, keep it out of the core. Promote it only after several customers have shown they can reuse it.
- How do you version configurations? A published version is frozen. Diffs between versions are easy for a person to read. Saving runs the schema check; publishing runs the compatibility check. Each config lives as a declarative document recording its tenant scope, version number, who wrote it and its rollout status. Production workflows only ever run against a pinned version, never a loose draft. Versions serve audit, support and reproducing customer incidents, not only rollback.
- When is a one-off fork acceptable? All three conditions must hold. Nobody else could reuse the requirement. The business value outweighs what it costs to support. And the divergence can be fenced off so it never seeps into the core. Make it a rare exception, not a pattern. One more valid case: refusing the fork would force the core into a shape that is less reliable or less secure for every other tenant.
- How do you sandbox extensions? Constrain what an extension can execute, which networks and data it can reach, and what side effects it can cause. Apply least privilege, explicit interfaces, resource limits and timeouts, and ship it as a deployment artifact a reviewer can inspect. When it handles customer data, document and audit exactly what it may read or write. Runtime isolation is one layer; sandboxing also covers interface design, review of dependencies and governed deployments.
Weak Answers and Their Repairs
- Weak: "Let's just make it all configurable." → Repair: draw a line between declarative customisation and business logic.
- Weak: "A flexible plugin system will cover it." → Repair: name the trust boundaries, the permissions and the review gates.
- Weak: "If we need a fork, we can add one later." → Repair: count what forks add to support, testing and every upgrade.
- Weak: "Backward compatibility is always a good thing." → Repair: explain how old versions retire and how big the compatibility budget is.
- Weak: "We'll sort out the details once it's launched." → Repair: make rollout depend on validation, observability and rollback.
Scoring Rubric
| Criterion | 1 — Weak | 3 — Adequate | 5 — Excellent |
|---|---|---|---|
| Discovery | Starts designing before asking what varies or what constrains it. | Asks a handful of basic questions about the customers and workflows. | Finds the hidden constraint fast, separates core from config, and states assumptions plainly. |
| Estimation | Frames no scale, or waves a hand at it. | Gives a rough scale but never ties it to a design choice. | Lets illustrative numbers justify the architecture, SLOs, rollout and operational risk. |
| Architecture | Draws boxes with no data, control or trust boundaries. | Covers the main components but skips one or two flows. | Walks control flow and data flow end to end, including how configuration is checked, given a version and run. |
| Depth | Stays too shallow, or digs into internals that do not matter. | Balances high-level points and detail fairly well. | Goes deep where failure costs most and stays brief elsewhere. |
| Security | Bolts security on at the end. | Mentions basic auth and sandboxing. | Builds least privilege, audit, rollback and extension boundaries into the design from day one. |
| Delivery | Leaves out rollout and support. | Gestures at a phased launch. | Describes a staged rollout, observability, gating on config and how to recover. |
| Communication | Hard to follow, with no closing summary. | Mostly clear, but the structure is loose. | Leads with the outcome, flags assumptions, makes trade-offs explicit and ends on a tight executive summary. |
Interview Rehearsal Checklist
- Before any mock or real interview, check that you can do these five things with no notes:
- Say the customer outcome in a single sentence.
- Name the hidden constraint and the assumption that carries the most risk.
- Explain what goes in core product and what goes in configuration.
- Hold your ground on one trade-off under pressure.
- Finish with a short summary and the first rollout gate.
90-Second Architecture Summary
- "We keep the workflow core small and stable. Tenant differences go into versioned configuration: fields, approvals, branding and connector settings. Customers edit their config through guarded admin tools. Every config is validated and stored as an immutable version. It publishes only if it passes the schema, policy and cycle checks. At runtime, the engine loads whichever version is live, moves each workflow from state to state and writes audit events. So support can replay what any tenant saw. Extensions plug in only through a controlled boundary. Each gets explicit contracts and minimal privilege. The main trade-off is configuration against code. I'll reuse whatever I can. But I won't push custom business semantics into config if that weakens security or makes the system hard to maintain. My first production gate is one tenant cohort. Rollback, audit and the adapter contract tests must be proven in staging before we go wider."
Practice Plan
- Solo exercise: take a blank page and talk through the 50-minute plan out loud. Then compress it to 2 minutes and to 90 seconds.
- Pair mock: get a partner to cut in with a hard question, for example "What becomes core product?" or "When is a one-off fork acceptable?". Practise replying calmly, without sounding defensive.
- Implementation exercise: design a validator for versioned configuration. It must reject cycles in approval routing and insist on safe publish-time checks before any workflow goes live.
- Equation guidance: this section adds no new equation. Numbers stay in prose here. The capacity maths lives in the scale section in part 2, which keeps this walkthrough on trade-offs rather than derivations.
- Why it helps in the job market: forward-deployed engineers hold exactly this kind of conversation when they turn one customer's problem into a product feature that is safe to ship.
Tutorial 7 · 12 min — The full lesson: how to reason through this case from first question to rollout.
Coverage Notes
We reviewed this guide once against our 20-point checklist for design case studies. Almost every point was met on that first pass, so the notes below record the result and flag where the guide is thin.
Phase 1 — Problem Framing & Discovery
- Item 1 (turning the feature request into a business outcome): Met. Section 1 states what the business gains before it names any technology.
- Item 2 (who the stakeholders are): Met. Section 1 lists 8 stakeholder groups.
- Item 3 (questions whose answers reshape the design): Met by the interview tree of six questions in Section 2.
- Item 4 (splitting and ranking requirements): Met. Section 2 keeps must-have functional needs apart from non-functional targets that can be measured.
- Item 5 (what is out of scope): Met. Section 2 names five things the MVP (minimum viable product) leaves out.
Phase 2 — Estimation & Architecture
- Item 6 (rough sizing and capacity arithmetic): Met. Section 3 runs a worked estimate, a four-step envelope and a sensitivity table.
- Item 7 (cost per unit, split by driver): Partly met. Section 3 lists cost per validated version among its SLO (service level objective) dimensions, yet no table separates cost into its drivers.
- Item 8 (the whole architecture and how data moves through it): Met. Section 4 gives the architecture diagram, a table of components and a sequence diagram.
- Item 9 (records and API contracts): Met. Section 5 specifies the four records and the four API contracts, and each contract states its auth, idempotency and error codes.
- Item 10 (whether to build or buy, and which vendor or model): Not met. The guide compares configuration with code and core with adapters, but never weighs vendors or BPM products against each other.
Phase 3 — Trade-offs, Security & Reliability
- Item 11 (named trade-offs, each with a fair verdict): Met by the four pairs in Section 8.
- Item 12 (threats and the controls that answer them): Met by Section 6, which sets out four security controls.
- Item 13 (drills for failure modes and reliability): Met. Section 6 runs a loop drill in four parts, four more failure cases and a policy table with six rows.
- Item 14 (how the system is tested): Met. Section 5 includes contract tests and failure-injection tests; Section 6 adds a test for the publish-path invariant.
Phase 4 — Delivery, Governance & Communication
- Item 15 (metrics in layers, plus observability): Met. Section 7 names 6 metrics inside a framework of four layers.
- Item 16 (staged rollout, risk register and gates for rollback): Met. Section 7 sets out four rollout phases, a risk register with four rows and the conditions that trigger rollback.
- Item 17 (depth on regulation and governance): Not met. Beyond the general audit trail, the guide says nothing about data residency or industry audit regimes.
- Item 18 (responsible AI and risks past the obvious failure): Not met. No AI component exists in this design; every risk it frames concerns safe configuration and keeping tenants apart.
- Item 19 (how people adopt the change): Met. Section 7 tells the rollout story, assigns owners and tracks adoption and the number of forks.
- Item 20 (a structured talk plan and a rubric to score yourself): Met. Section 8 provides the minute-by-minute plan, the 90-second summary, a scoring rubric, a rehearsal checklist and a practice plan.
Four items fall short: 7, 10, 17 and 18. Each gap is a deliberate choice of scope or emphasis in the design, not something lost while writing it up, so a single review pass was enough.
My Perspective on the Gaps
These notes reach beyond the core design. Use them if an interviewer presses on one of the gaps.
Item 7 — cost per unit, split by driver.
- Base the cost model on Section 3's four-step capacity envelope. Four lines are enough. Line one prices each validated version: the CPU and network spent on schema, policy, dependency and dry-run checks. Line two prices each deployment; fan-out to tenant instances and downstream sync jobs drives it, while raw edit volume barely matters. Line three is the price of each second a plugin runs in the sandbox. Line four is storage for the version graph and the append-only
ConfigDeploymentledger. - Say one driver out loud: tenants, not the platform design, set validation cost. Forms with deep branching or rules with many clauses make validation dearer even when nothing on the platform side moves. So the limits on expressive power in Section 3 (maximum fields, maximum rule clauses, bounded plugin windows) control cost as much as they control safety.
- Report cost per tenant, line by line. One noisy tenant then shows up clearly instead of disappearing into a platform total. That view turns "should this customisation sit in config or in an adapter?" into a debate about real figures rather than about leverage in the abstract.
Item 10 — whether to build or buy, and which vendor or model.
- Here the obvious build-or-buy question is the workflow and rules engine. You could build the declarative engine laid out in Sections 4 and 5. Or you could buy a BPM or workflow-orchestration product (a commercial rules engine, say, or workflow-as-a-service) and keep the same split between control plane and data plane around it.
- A rule of thumb: buy what is already solved and commoditised, where the vendor owns its security record and competes on it. Identity and SSO (single sign-on) federation is the standard example, and it fits here if customer admins must log in through an enterprise IdP. Build the workflow engine, though. What sets this platform apart is its constrained, tenant-safe configuration model, with validation, versioning and sandboxing. No vendor can take responsibility for that in your place. It is also what the interviewer wants to see you design.
- The adapter SDK falls between the two. Where a connector framework already exists for a common system, such as CRM (customer relationship management) or ticketing, reuse or buy it. Keep the adapter contract and its permission model in-house: the security design in Section 6 depends on that trust boundary.
Item 17 — depth on regulation and governance.
- Section 4 already isolates tenants: it separates control plane from data plane, partitions data by tenant ID and scopes each adapter to one tenant. Data residency extends that. When customers live under different jurisdictions, the configuration registry and the workflow audit store may need storage fixed to a region. Section 5's
TenantConfigandConfigDeploymentrecords would each gain a region field. The deployment ledger could then show where every tenant's data was held and where its workflows ran. - Tie this to the audit control in Section 6, which logs who published each config version. That durable log already answers most of what a SOC 2 auditor (criterion CC8.1) or a review of SOX (Sarbanes-Oxley Act) IT general controls asks about change management: who signed off, what was tested and what shipped. It does less for HIPAA (Health Insurance Portability and Accountability Act) if the workflow handles health data, because HIPAA's audit-control rule targets activity on any system storing that data, beyond config changes alone. The real gap is therefore modest. What it mostly lacks is an explicit retention period per regulation, added to the retention rules Section 5 already sets.
- Bring this up in an interview before anyone asks. Pick one regulation that matches the customer's industry; SOX-style segregation of duties for approval workflows in financial services is a good example. Then show how the existing approval roles and audit trail satisfy it, with no new infrastructure.
Item 18 — responsible AI and risks past the obvious failure.
- Nothing in this design is AI. Its "intelligence" consists of deterministic validation, policy checks and rule evaluation. Bolting on a responsible-AI story would sound hollow. Tell the interviewer that directly instead of reaching for generic AI-safety wording.
- That may change. A later release might add an AI helper for authoring, say one that drafts an approval graph from a plain-language description. The fail-closed stance of Section 6 applies unchanged. Whatever the AI proposes is untrusted input. It passes through the same schema, policy and cycle-detection gates as config a person wrote, and it never gets a route around them.
- Under pressure from the interviewer, state the trade-off. AI-assisted authoring adds risk at the very boundary this design already treats as hostile: the path from config editor to validator in Section 4. It raises the volume of untrusted input that reaches the validator. The trust model stays the same.
Key Takeaways
- Reframe "configuration" as the outcome: one stable product core with customisation that is safe, versioned and supportable, so ten customers never become ten forks.
- Measure how often a new customer variation ships with no fork, no hotfix and no support escalation, not how many fields are configurable.
- Who writes the configuration is an architecture question, because customer admins need validation, previews, narrow building blocks and safe defaults.
- Size for the peak: 1,000 config versions a day is about 42 an hour, but half of them inside a 2-hour window is about 250 an hour before retries.
- Keep validation out of the save API, so save stays fast and durable, validation is queued and bounded, and deployment is idempotent and retryable.
- Deployment capacity is set by fan-out across tenants, regions and integrations, not by the number of human edits.
- Split the control plane, where configs are written, checked, versioned and promoted, from the data plane that runs live workflows, so risky change stays off the hot path.
- Publishing an immutable version is not switching it on: a tenant flag exposes it to one tenant, then a cohort, and rollback flips the flag back to the previous version.
- The append-only
ConfigDeploymentledger, not the config record, says which version is live, so a rollback is a new event and never an edit in place. - Publish runs parse, validate, authorise-by-policy, persist in that order, so cheap structural checks fail fast and nothing is written until every check passes.
- Create-style calls carry an idempotency key: the same key and body return the same deployment, a changed body gets
422and a retry still in flight gets409. - Arbitrary code is off by default, and any approved extension runs in a sandbox with least privilege, resource caps and limits on outbound traffic.
- An infinite approval loop is caught by cycle detection at publish or a max-hop guard at runtime, and fails closed for that version while the tenant falls back to the last known-good one.
- Partition by tenant ID so one tenant's approval surge stays in its partition; at 100 times the baseline that becomes per-tenant quotas and sharded queues.
- Widen the configuration surface only after a second and third customer reuse the same mechanism, and steer by fork count and upgrade time per tenant.
Level calibration
On a shared workflow platform for ten customers, the levels separate on how far the answer goes past "make it configurable".
| Level | Sounds like | Missing |
|---|---|---|
| Senior | "One workflow runtime, declarative configs in a versioned registry, a validator on publish and an adapter SDK for integrations. Save stays fast while validation and deployment run queued and idempotent, and an approval loop fails closed for that version." | Who authors and approves configs, how a new version reaches one tenant before the rest, and who owns rollback. |
| Staff | "Split the control plane from the data plane: publish immutable versions, expose them behind a tenant flag and gate on the tenant test harness. Ownership is explicit: product owns templates, customers own values, integrations own adapters, ops own the ConfigDeployment ledger, and rollout starts with one willing customer admin." |
Which variations earn a place in core, how old schema generations retire, and whether to build the engine at all. |
| Principal | "Pick the workflow family the company wants to own and promote a capability to core only when its leverage is proven. Build the constrained configuration model, buy commodity pieces like identity federation, and run a deprecation policy with a small number of supported generations. Fork count and upgrade time per tenant are what the organisation steers by." | Which differences truly recur, and what upgrade promise customers expect, need the first customers' real change requests to settle. |
Sources (checked 27 Sep 2026)
- The Idempotency-Key HTTP Header Field (IETF draft -07) —
422for a reused key with a different body,409for a retry while the first request is still running, replay returns the original result - RFC 9110: HTTP Semantics — meanings of
409 Conflict,412 Precondition Failedfor a failedIf-Match, and the422 Unprocessable Contentname - AICPA 2017 Trust Services Criteria (revised points of focus, 2022) — SOC 2 change-management criterion CC8.1
- PCAOB AS 2201: An Audit of Internal Control Over Financial Reporting — SOX audits test IT controls, including program changes and segregation of duties
- 45 CFR 164.312(b), HIPAA audit controls — HIPAA's audit rule covers activity on systems holding health data