When modeling a 'place order' use case for an e-commerce checkout, what belongs in the main success scenario versus the alternate and exception flows, and why does keeping them separate matter for the resulting architecture?
answer
- happy path = main success scenario
- alternate flow = valid variation, still succeeds
- exception flow = failure needing explicit handling
- exception flows drive saga/idempotency/retry design
- prioritize by likelihood times impact, not exhaustiveness
basics
~20 sThe main flow is the happy path: everything goes right and the order completes. Alternate and exception flows are what happens when something differs or goes wrong, like a declined card or an out-of-stock item. Keeping them separate matters because each exception often needs its own piece of architecture to handle it.
solid answer
~40 sA use case's main success scenario is the single, linear sequence of steps where every precondition holds and every step succeeds, e.g. select items, enter payment, authorize, confirm order. Alternate flows are valid variations that still succeed but branch (e.g., apply a promo code), while exception flows are failures that need explicit handling (declined payment, item goes out of stock between add-to-cart and checkout, session timeout). Separating them matters architecturally because the main flow tells you the core data model and happy-path latency budget, while the exception flows tell you what compensating actions, retries, idempotency guarantees, and UI states the system needs, and those often dominate the actual implementation effort and are the parts most likely to need saga-style or distributed-transaction handling.
go deeper
Can write a clear main success scenario for a simple use case and name at least one obvious exception flow when prompted.
Independently identifies most relevant exception flows for a use case and can explain what each implies for the design, such as needing a retry or rollback.
Prioritizes exception flows by likelihood and impact, translates them into concrete architectural mechanisms such as sagas, idempotency, or holds, and pushes back when a use case is under-specified before implementation starts.
Establishes the org's convention for how deep exception-flow analysis should go per system criticality, and recognizes cross-use-case exception patterns that justify a shared platform mechanism, such as a general-purpose reservation or hold service, rather than one-off fixes per feature.
## Main success scenario, and everything layered on it Use-case modeling is a technique for capturing system behavior from the perspective of an actor pursuing a goal, structured as a sequence of interactions between that actor and the system. The convention splits a use case into a main success scenario and a set of extensions. The main success scenario (sometimes called the 'happy path') is the single, unbranching sequence of steps that occurs when every precondition holds and every step succeeds: for 'place order,' that's something like 'customer reviews cart, enters shipping address, selects payment method, system authorizes payment, system creates order, system confirms to customer.' It's deliberately written as one clean, linear story, because its job is to establish the baseline shape of the interaction - the minimum set of steps and data needed for the use case to exist at all. Extensions are then layered on top as either alternate flows or exception flows. | Extension | What it is | Cases | |---|---|---| | **alternate flows** | valid variations that still lead to success | like applying a discount code, choosing gift wrapping, or selecting expedited shipping | | **exception flows** | failures or edge conditions that require the system to do something other than proceed | such as a declined card, an item going out of stock between 'add to cart' and 'place order,' a session timing out mid-checkout, or a fraud-detection hold | ## The two jobs the split does This structure exists because it separates two very different jobs an architect and analyst need to do. - **Nailing the main flow** answers 'what is this feature, fundamentally' - what entities exist (Order, Cart, Payment), what the core data model needs, and what the baseline performance and latency budget looks like for the common case. - **Enumerating the alternate and exception flows** answers a completely different and usually much larger question: 'everywhere this can go wrong, what does the system owe the user and the business, and what architecture makes that possible?' In most real systems, the main flow is a small fraction of the actual code and infrastructure. The exception flows - payment failures, inventory races, timeouts, retries, partial failures across services - are what drive decisions like: - whether payment authorization and order creation need to be one atomic transaction or a saga with compensating actions - whether inventory needs a reservation or hold mechanism with a TTL - whether the checkout API needs to be idempotent so a retried request after a timeout doesn't double-charge a customer - what UI states need to exist beyond 'success' and 'generic error.' ## Thoroughness versus paralysis The trade-off in use-case modeling is thoroughness versus paralysis. Enumerating every conceivable exception flow for every use case is not free - it takes analyst and stakeholder time, and an exhaustive list for a simple use case can balloon into dozens of edge cases, most of which are low-probability and low-impact. Good practice is to prioritize exception flows by a rough cost and likelihood estimate: - 'payment declined' is common and high-impact, so it gets full design attention (specific UI copy, retry affordance, no order created) - 'the customer's browser crashes exactly between authorization and order confirmation' is rare but high-impact for a payments system, so it still needs explicit handling (idempotency, reconciliation) even though it's unlikely - 'the customer's session theme preference fails to load' is low-impact and can be handled generically Skipping this prioritization in either direction fails: skip it toward completeness and requirements analysis never finishes; skip it toward the happy path only and the architecture ships without the mechanisms that real, messy traffic will immediately need. ## The bug class this prevents The failure mode of not separating flows, or of not enumerating exception flows at all, shows up in production as a very specific and common class of bug: happy-path features that behave correctly in a demo and then corrupt state or double-charge customers under real, imperfect network conditions. - A checkout flow designed only against the main success scenario typically has no answer for 'the payment gateway responded successfully but the response was lost due to a network blip, and the client retried' - without an exception flow calling out this case explicitly during analysis, the architecture has no idempotency key, and the retry creates a second charge. - Similarly, 'item goes out of stock between add-to-cart and checkout' not being modeled as an exception flow tends to produce race-condition bugs where two customers are both told their order succeeded for the last unit of an item, because nobody designed an inventory-reservation step to handle that concurrent scenario. ## The checkout saga A concrete, well-known real-world pattern that comes directly out of this kind of exception-flow analysis is the checkout saga used by large e-commerce platforms: 1. payment authorization 2. inventory reservation 3. shipping-label creation These are separate steps, each with a defined compensating action (release the inventory hold, refund the authorization) if a later step in the sequence fails - a design that exists entirely because someone modeled the exception flows, such as 'what if inventory reservation succeeds but shipping label creation fails,' as first-class scenarios rather than leaving them as unhandled edge cases discovered in production.
- How do exception flows in a use case translate into non-functional or architectural requirements?Each exception flow usually implies a specific mechanism: 'payment gateway times out' implies idempotency and retry-safety; 'item goes out of stock mid-checkout' implies an inventory reservation with a hold or TTL; 'user session expires mid-flow' implies state that survives a re-login. These get written into the architecture as concrete patterns, such as sagas, holds, or idempotency keys, rather than staying as prose in the use case.
- How do you decide which exception flows are worth designing for versus accepting as a generic error?Weigh likelihood against business impact: high-likelihood or high-impact exceptions, such as declined payment or an inventory race in a payments-adjacent flow, get dedicated design; low-likelihood, low-impact ones, such as a rare client rendering glitch, can fall back to a generic error state. This keeps analysis effort proportional to risk instead of trying to enumerate every conceivable failure.
- What's the difference between an alternate flow and an exception flow in use-case terms?An alternate flow is a valid variation that still ends in success, such as applying a coupon or choosing a different payment method, just a different path to the same successful outcome. An exception flow is a failure or abnormal condition that prevents the use case from completing normally and requires the system to do something corrective, like rolling back a reservation or showing a retry option.
A use case is like a recipe: the main success scenario is the recipe as written (measure, mix, bake, done), while the alternate and exception flows are the footnotes experienced cooks actually need - what to do if the oven runs hot, if you're out of an ingredient, if the batter curdles. Skip the footnotes and the recipe only works in the one kitchen where nothing ever goes wrong.
saying these in an interview costs you the question
- designs and tests only the happy path
- treats every exception as 'show a generic error' without considering data consistency
- can't distinguish alternate flow from exception flow
- enumerates exception flows exhaustively with no prioritization, stalling delivery
- doesn't connect exception flows to concrete architecture mechanisms like idempotency or compensation