Overview
A saga manages a business transaction that spans multiple services by breaking it into a sequence of local transactions, each in one service. If a step fails, the saga runs compensating transactions to semantically undo the steps that already succeeded, for example refunding a payment or releasing reserved stock. This provides eventual consistency without distributed locks or two-phase commit.
Sagas can be choreographed (each service reacts to events and emits the next) or orchestrated (a central orchestrator sends commands and tracks state). Orchestration is easier to understand and monitor for complex flows; workflow engines like Temporal, AWS Step Functions, and Camunda make orchestrated sagas durable and observable.
You book a flight, then a hotel, then a car. If the car rental fails, you do not un-happen the flight; you cancel the hotel and the flight (compensations). Each booking is independent, and cancellations restore a consistent state.
When to use it
- Multi-service business workflows: checkout, booking, account opening.
- Long-running processes that cannot hold locks.
- Microservices with a database per service.
- Workflows that need clear compensation logic.
Where it shows up in interviews
Recognize it when: order, payment, and inventory must stay consistent.
- Design e-commerce checkout with microservices
- Design a travel booking system
- Design a food delivery order flow
Where it is used in real software
Used by Uber, Netflix, Stripe, and Snap for durable workflow orchestration with retries and compensations.
State machines orchestrating Lambda and services, with built-in retries and catch blocks for compensation.
Chris Richardson's framework for choreography and orchestration sagas with the outbox pattern.
Key terms
- Local transaction
- An ACID transaction within one service.
- Compensating transaction
- Semantic undo of a completed step.
- Orchestrator
- Central coordinator sending commands and tracking state.
- Pivot transaction
- Point after which the saga must complete (cannot compensate).
- Semantic lock
- Marking a record as pending to prevent conflicting changes.
How it works, step by step
- 1Start the saga
Create the order in PENDING state.
- 2Execute steps
Reserve inventory, charge payment, schedule delivery.
- 3On failure, compensate in reverse
Refund payment, release inventory, cancel order.
- 4Make every step idempotent
Retries and duplicate messages are expected.
- 5Complete
Mark the order CONFIRMED; persist saga state throughout.
STEP 1Step 1: create order o-981 as PENDING.
Steps and compensations
Checkout saga
| Step | Action | Compensation |
|---|---|---|
| 1 | Create order (PENDING) | Mark order CANCELLED |
| 2 | Reserve inventory | Release reservation |
| 3 | Authorize payment | Void authorization / refund |
| 4 | Schedule shipment | (pivot: after this, retry until success) |
| 5 | Confirm order | - |
NOWStep: 1 | Action: Create order (PENDING) | Compensation: Mark order CANCELLED
Order steps so the hardest-to-compensate actions (shipping, sending emails) come last, after the pivot.
Implementation
import { proxyActivities } from "@temporalio/workflow";import type * as activities from "./activities"; const { createOrder, cancelOrder, reserveStock, releaseStock, chargePayment, refundPayment, scheduleShipment, confirmOrder } = proxyActivities<typeof activities>({ startToCloseTimeout: "30 seconds", retry: { maximumAttempts: 5 } }); // Durable workflow: state survives crashes; each activity is idempotentexport async function checkoutSaga(input: { orderId: string; customerId: string; items: Item[]; totalCents: number }) { const compensations: (() => Promise<void>)[] = []; try { await createOrder(input); compensations.unshift(() => cancelOrder(input.orderId)); const reservationId = await reserveStock(input.orderId, input.items); compensations.unshift(() => releaseStock(reservationId)); const paymentId = await chargePayment(input.orderId, input.customerId, input.totalCents); compensations.unshift(() => refundPayment(paymentId)); await scheduleShipment(input.orderId); // pivot await confirmOrder(input.orderId); } catch (err) { for (const undo of compensations) await undo(); // reverse order throw err; }}Complexity and performance
Commands and replies.
Intermediate states visible.
Trade-offs
Sagas avoid distributed locks and stay available, but other requests can see intermediate states (lack of isolation); use semantic locks like PENDING statuses.
Choreography is decoupled but hard to follow; orchestration is explicit and observable but centralizes flow logic.
Variants and related techniques
Services react to each other's events.
A workflow engine or saga orchestrator drives steps.
Reserve resources first, then confirm or cancel all.
Common mistakes
- Non-idempotent steps or compensations.
Fix: Every step will be retried; use idempotency keys.
- Compensations that can fail permanently.
Fix: Design compensations to always eventually succeed; retry and alert.
- Irreversible actions early (sending an email).
Fix: Place them after the pivot.
Interview questions
What is a saga and why use it instead of 2PC?
A saga is a sequence of local transactions with compensating actions on failure. It avoids 2PC's blocking locks and tight coupling across services, providing eventual consistency and high availability in microservices.
Orchestration or choreography for checkout?
Orchestration for the core checkout: the steps, compensations, timeouts, and state are explicit and observable in one place (Temporal or Step Functions). Notifications and analytics can react to events via choreography.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Define steps and compensations for hotel booking | Medium | Compensation design. |
| Implement an orchestrated saga with retries | Hard | Durable workflows. |