Designing a Multi-Vendor Marketplace Cart and Checkout System

Designing a Multi-Vendor Marketplace Cart and Checkout System

Designing a Multi-Vendor Marketplace Cart & Checkout System

How platforms like Amazon Marketplace, Etsy, Flipkart, and Alibaba let a single shopping cart hold items from dozens of independent sellers — and still check out as one smooth transaction, with split payments, partial-failure handling, and per-vendor fulfilment behind the scenes.

01

Introduction and History

One cart, one checkout, many independent shopkeepers — and a gap between “who owns the truth” and “who owns the customer experience” that shapes every design decision that follows.

Imagine walking into a giant mall. You pick up a phone charger from one shop, a t-shirt from another, and a coffee mug from a third — and then you walk to a single checkout counter at the mall entrance and pay once for everything. Behind the scenes, the mall still has to pay each individual shopkeeper their share, track each item separately, and let each shop ship or hand over its own goods. That is exactly what a multi-vendor marketplace does on the internet — except the “shops” are independent sellers, and the “mall” is a platform like Amazon, Etsy, Flipkart, eBay, or Alibaba.

A multi-vendor marketplace cart and checkout system is the part of an e-commerce platform that allows a shopper to add products from many different, independent sellers into one shopping cart, and then complete one checkout — one payment, one order confirmation screen — even though, internally, that single action may need to split into several independent sub-orders, several separate payments to different seller accounts, and several separate shipments that arrive on different days from different warehouses.

In the early days of e-commerce (late 1990s), most online stores were single-vendor: one company sold its own inventory, from its own warehouse, through its own website. Amazon itself started this way in 1994, selling only its own books. The shift toward multi-vendor marketplaces began around the early-to-mid 2000s, when Amazon launched “Amazon Marketplace” (2000) to let third-party sellers list products alongside Amazon’s own catalog, eBay formalised itself as a marketplace for independent sellers, and Alibaba built Taobao (2003) as a platform purely for independent merchants. This was a fundamentally different engineering problem than a single-vendor store, because now the platform had to coordinate money, inventory, and logistics across businesses it did not own or directly control.

Today, multi-vendor marketplaces are one of the dominant e-commerce models worldwide — Amazon Marketplace, Etsy, Flipkart, Walmart Marketplace, Alibaba/Taobao, Shopee, Noon, and thousands of niche marketplaces (food delivery apps aggregating restaurants, ride-hailing apps aggregating drivers, freelance platforms aggregating service providers) all share the same underlying engineering pattern: one cart, one checkout, many independent fulfilment parties.

1.1 How Marketplace Architecture Has Evolved

1994–1999

Single-Vendor E-Commerce Era

Amazon and early online stores sell only their own inventory — every consistency guarantee can, in principle, live inside one well-designed relational database.

2000

Amazon Marketplace Launches

Amazon opens its catalog to third-party sellers, giving the platform a new class of problem: coordinating money, inventory, and shipping across businesses it does not own.

2003

Taobao Is Built Multi-Vendor by Design

Alibaba launches Taobao as a marketplace built entirely around independent sellers from day one — forcing the underlying platform to solve split payments, per-vendor fulfilment, and cross-vendor consistency as first-class problems.

2005–2012

Unified Cart Becomes an Expectation

Etsy, Flipkart, and regional marketplaces emerge; “unified cart across sellers” becomes a customer expectation rather than a differentiating luxury.

2015–Present

Microservices, Events and the Saga Pattern

Microservices, event-driven architecture, and the Saga pattern become the standard way to build multi-vendor checkout at scale, replacing older monolithic, single-database order systems.

It’s worth pausing on why this transition mattered so much from an engineering point of view, not just a business one. A single-vendor store is, architecturally, a relatively contained problem: one company controls the catalog, one company controls the warehouse, and one company controls the money. Every consistency guarantee can, in principle, live inside one well-designed relational database. The moment a platform invites in independent third-party sellers, that comfortable assumption disappears. The platform no longer owns the inventory truth — the vendor does. The platform no longer owns the shipping truth — the vendor’s courier does. And yet the platform is the one presenting a single, trustworthy checkout experience to the customer. This gap between “who actually knows the truth” and “who is responsible for showing a consistent story to the customer” is the single thread that runs through almost every architectural decision described in this article.

It also helps to understand who the stakeholders are, because a multi-vendor marketplace is really three products stitched into one: a shopping experience for the customer, an operations dashboard and payout system for the vendor, and an internal risk, fraud, and logistics control plane for the platform itself. Every architectural component described later in this tutorial — the Cart Service, the Order Orchestrator, the Payment Service — exists because it serves at least one, and usually all three, of these stakeholders simultaneously.

Finally, it is worth noting how this problem generalises far beyond retail. The same “one cart, many independent providers” shape appears in food-delivery apps combining dishes from multiple restaurants in one delivery run, in travel-booking platforms bundling flights, hotels, and car rentals from different suppliers into a single itinerary purchase, and in freelance/services marketplaces where a single project payment may need to be split across several independent contractors. Once you understand the multi-vendor cart and checkout pattern deeply, you effectively understand a large family of real-world distributed-systems problems, which is exactly why this topic is such a popular one in senior and staff-level system design interviews.

02

Problem and Motivation

Why not just force the shopper to check out three times if they bought from three sellers? Because that single decision destroys the shopping experience — and everything that follows in this article is a consequence of preserving it.

Why not just treat each vendor’s items as a completely separate purchase — force the shopper to check out three times if they bought from three sellers? Because that destroys the shopping experience. Studies from every major marketplace show that requiring multiple checkouts sharply increases cart abandonment. Customers want one address form, one payment entry, one “Place Order” button, and one order confirmation — regardless of how many sellers are involved behind the curtain.

This single requirement — “one checkout experience, many independent backends” — creates a cluster of genuinely hard distributed-systems problems:

CHALLENGE

Split Payments

One payment charge from the customer must be divided and routed to multiple vendor accounts, each potentially with different commission rates, tax rules, and payout schedules.

CHALLENGE

Partial Failure

What happens if Vendor A has stock but Vendor B just sold out the last unit half a second earlier? The system must handle partial success gracefully, not fail the entire cart.

CHALLENGE

No Global Transaction

Vendor inventory, cart state, payment, and shipping often live in different databases or even different services owned by different teams — a single ACID transaction across all of them is not realistic at scale.

CHALLENGE

Independent Fulfilment

Each vendor ships from their own warehouse on their own schedule. One “order” the customer sees is actually N independent shipments with N independent tracking numbers.

CHALLENGE

Vendor-Specific Rules

Different vendors may have different shipping costs, minimum order values, return policies, and regional restrictions — all of which must be respected inside one unified cart.

CHALLENGE

Consistency at Scale

During flash sales, thousands of shoppers may have the same item from the same vendor in their carts simultaneously — the system must prevent overselling without collapsing throughput.

The Core Tension

Traditional relational databases give strong guarantees through ACID transactions, but only within a single database. Multi-vendor checkout, by its very nature, spans many databases owned by many services. This forces architects to trade strict, immediate consistency for eventual consistency, coordinated through patterns like the Saga pattern, in exchange for scalability and vendor independence.

There is also a subtler, second-order problem that many designs get wrong on the first attempt: ownership boundaries under failure. When something goes wrong mid-checkout — a payment declines, a vendor’s stock sells out to someone else a moment earlier, a shipping-zone restriction is discovered too late — the system has to decide, cleanly, which parts of the attempted purchase survive and which parts get rolled back, without ever leaving a customer in an ambiguous state such as “charged but no confirmed order” or “confirmed order but item never actually reserved.” Getting this wrong doesn’t just create a bad user experience; it creates real financial and legal exposure, refund disputes, and vendor trust problems, because vendors are effectively business partners of the platform, not just internal departments.

Another dimension of the problem is fairness and isolation between vendors. Because dozens, hundreds, or even millions of independent sellers share the same underlying platform infrastructure, the system has to guarantee that one vendor’s behaviour — a traffic spike from their own marketing campaign, a buggy inventory-sync integration hammering the API, or an unusually large flash sale — cannot degrade checkout reliability for every other vendor on the platform. This “noisy neighbour” problem doesn’t exist at all in a single-vendor store and becomes a first-class architectural concern the moment the platform is multi-tenant by seller.

Finally, there is a business-logic dimension that is easy to underestimate: every vendor may have different shipping rules, different regional restrictions (a vendor may not be able to legally ship certain goods to certain states or countries), different tax obligations depending on where they are registered as a business, and different minimum order values or bulk discount rules. The cart and checkout system has to apply all of these vendor-specific rules correctly and transparently, in real time, while still presenting the customer with one clean, understandable total at the end.

i
What an Interviewer May Ask
  • “Why can’t we just wrap the whole checkout in one database transaction?” — Because inventory, payment, and vendor data typically live in separate services/databases (microservices), and distributed two-phase commit does not scale or tolerate partial failures well.
  • “What’s the single biggest UX constraint driving this architecture?” — The customer must experience exactly one checkout flow, regardless of how many vendors are involved internally.
  • “How would you prevent one vendor’s traffic spike from affecting other vendors’ checkouts?” — Per-vendor rate limiting and resource isolation (bulkheads) at the gateway and inventory layers, so a single seller’s load cannot exhaust shared thread pools or connection pools.
03

Core Concepts

Before we can draw a single box in an architecture diagram, we need shared vocabulary for the seven concepts that every following section will keep referring back to.

Before we dive into architecture, it helps to be very precise about the ideas that this system revolves around. Getting these concepts right in your own head is what separates a design that looks reasonable in a diagram from one that actually holds up under load and failure.

CONCEPT 3.1

Vendor (Seller / Merchant)

An independent business or individual selling products through the platform. Vendors have their own inventory, their own bank account for payouts, their own shipping arrangements, and often their own return/refund policies. The platform is the intermediary, not the seller.

CONCEPT 3.2

Unified Cart

A single logical cart, owned by one customer, that can hold items from many vendors. Internally, it’s often modelled as a list of items grouped by vendorId, but externally it appears as one seamless list.

CONCEPT 3.3

Parent Order & Sub-Orders

When a customer checks out, the platform creates one parent order (what the customer sees) and one sub-order per vendor (what each vendor sees, ships, and gets paid for). This split is one of the most important design ideas in the whole system.

CONCEPT 3.4

Split Payment / Payout

The total charged to the customer is split across vendors, with the platform typically taking a commission from each vendor’s share. Payouts to vendors usually happen on a scheduled cycle (e.g., weekly), not per order.

CONCEPT 3.5

Inventory Reservation

A short-lived hold on stock during checkout, so an item in one customer’s cart at the moment of payment isn’t sold to someone else in a race. This is a soft lock with a TTL, not a permanent deduction — permanent deduction only happens on successful payment.

CONCEPT 3.6

Saga Pattern

A pattern for coordinating a long-running business transaction across multiple services, using a sequence of local transactions with explicit compensating actions (rollbacks) for each step. This replaces the impossible “one big distributed transaction.”

CONCEPT 3.7

Idempotency

The property that performing the same operation multiple times has the same effect as performing it once. Crucial for payments and order creation, since network retries are inevitable and must never cause double charges.

1
Parent Order per checkout
N
Sub-orders (one per vendor)
1
Customer payment charge
N
Vendor payout entries

A very common source of confusion in interviews is treating the parent order and the sub-orders as if they were the same kind of thing at different granularities. They’re not. The parent order exists almost entirely for the customer’s benefit — it’s the object attached to the confirmation email, the “my orders” page, and the single reference number a support agent asks for. The sub-orders exist almost entirely for the vendors’ and the platform’s benefit — they’re the atomic unit of fulfilment, the record against which shipping happens, and the accounting object for payouts and commission. Once you internalise this “customer sees parent, operations happens on sub-orders” distinction, most of the confusing state-management questions later in the system essentially answer themselves.

It’s also useful to notice how the Saga pattern and idempotency actually work together in practice. A saga defines what the multi-step business transaction is and what to do if a middle step fails; idempotency defines how safely each individual step can be retried without causing damage if the network drops a response or a service temporarily disappears. Neither concept, on its own, is enough for a marketplace checkout: sagas without idempotency risk double-charging on retries, and idempotency without a saga still leaves inconsistent state when one vendor’s reservation succeeds and another’s doesn’t. They’re a pair, and both are load-bearing in the architecture that follows.

One last concept worth introducing here, because it’s often skipped in shorter treatments of this topic: the difference between a reservation and a commitment of inventory. A reservation is a soft, time-bounded promise (“this unit is held for this checkout for the next N minutes”) that can naturally expire if nothing further happens, and can be released cheaply on cancellation. A commitment is the final, permanent deduction from available stock that happens only after payment succeeds and the sub-order is confirmed. Conflating the two — simply deducting stock at add-to-cart, for instance — leads directly to abandoned carts silently locking away inventory forever, which is one of the most common bugs in first-attempt marketplace designs.

04

Architecture and Components

Every request enters through the Load Balancer and API Gateway before reaching any microservice, and the Order Orchestrator coordinates the Saga across Inventory, Payment, and Shipping.

Client Layer Client Apps — Web, Mobile, PWA Edge & Entry Layer CDN — Static Assets Load Balancer (L7, TLS, Health) API Gateway (AuthN, Rate Limit, Routing) Application Microservices Auth ServiceJWT, OAuth2 Cart Servicegrouped by vendor Catalog Serviceproducts, listings Pricing & Promoscoupons, tax Order OrchestratorSaga coordinator Inventory Service Payment Service Shipping & Fulfilment Notification Service Async Backbone & Cache Message Broker (Kafka) Cache Layer (Redis) Data Stores Cart DBNoSQL, shardedby user ID Catalog DBsharded byvendor ID Order DBparent + suborders Inventory DBper-vendor stockcounters Payment Ledger DBACID, per-vendorpayout records Vendor Fulfilment APIsindependent sellersystems (external) Observability Stack Metrics + Logs + Distributed Tracing (Jaeger / Datadog)
Fig 4.1 — End-to-end architecture. Every request enters through the Load Balancer and API Gateway before reaching any microservice, and the Order Orchestrator coordinates the Saga across Inventory, Payment, and Shipping.

4.1 What Each Component Actually Does

ComponentResponsibilityInterviewer Angle
Load BalancerDistributes incoming traffic across API Gateway instances; terminates TLS; performs health checks; removes unhealthy nodes.Why L7 not L4? Because routing decisions (path, header-based) require inspecting HTTP content.
API GatewaySingle entry point; authenticates requests, applies per-vendor and per-user rate limits, routes to the correct microservice, can aggregate multiple backend calls into one response.How do you prevent the gateway becoming a single point of failure? Run it stateless behind the load balancer, horizontally scaled.
Cart ServiceOwns cart state; groups items by vendorId; validates item availability and price on every cart mutation.Why store carts grouped by vendor instead of a flat list? So checkout-time splitting into sub-orders is O(1) grouping, not a runtime computation.
Order OrchestratorImplements the Saga: creates parent order, splits into sub-orders, coordinates reservation, payment, and shipping steps, triggers compensations on failure.This is the heart of the interview — expect deep questions on Saga choreography vs. orchestration here.
Inventory ServiceOwns per-vendor stock counts; exposes reserve/release/commit stock APIs with strong consistency per vendor shard.How do you avoid overselling under high concurrency? Optimistic locking or atomic decrement operations.
Payment ServiceCharges the customer once, then splits settlement across vendor sub-accounts; maintains an immutable ledger.How do you handle a payment gateway timeout? Idempotency keys plus asynchronous webhook confirmation, never blind retries.
Shipping / Fulfilment ServiceNotifies each vendor of their sub-order; tracks shipment state independently per vendor; aggregates status back to the parent order.How does the customer see one “order status” from N independent shipments? The parent order’s status is derived (e.g., “Partially Shipped”) from the min/max of sub-order states.
Message Broker (Kafka)Decouples the orchestrator from downstream services; provides durability and replay for order events.Why not direct synchronous calls everywhere? Because a vendor-facing downstream service being slow would otherwise block the entire checkout.
Cache Layer (Redis)Serves hot cart reads, session data, and rate-limit counters without hitting the primary database.What do you cache and for how long? Cart contents (short TTL, invalidated on write), catalog price/availability snapshots (seconds-level TTL).
i
What an interviewer may ask
  • “Why is there both a Load Balancer and an API Gateway — aren’t they redundant?” — The load balancer handles low-level traffic distribution and TLS; the gateway handles application-level concerns like auth, rate limiting, and routing to specific microservices. In small systems they can merge, but at marketplace scale they’re kept separate for independent scaling.
  • “Where would you put a Circuit Breaker in this diagram?” — Between the Order Orchestrator and each downstream service (Inventory, Payment, Shipping) to stop cascading failures if one vendor-facing dependency is slow.

4.2 Walking Through One Request, Box by Box

It helps to trace one concrete request through Fig 4.1 end to end. A customer taps “Checkout” on their phone. The request first hits the CDN only if it needs a static asset (the checkout page’s JavaScript bundle, for instance); the actual checkout API call bypasses the CDN and goes straight to the Load Balancer, which terminates TLS and forwards the request, based on a health-checked round-robin decision, to one of many identical API Gateway instances. The gateway validates the customer’s JWT with the Auth Service (or a cached, signature-verified token check that avoids a network hop on every request), applies a per-user rate limit, and routes the call to the Order Orchestrator.

The orchestrator does not talk to raw databases directly for cart contents — it asks the Cart Service, which itself first checks the Cache Layer before falling back to the Cart Database. Once the orchestrator has the vendor-grouped cart, it fans out reservation requests to the Inventory Service, which enforces atomic, per-vendor stock decrements against the Inventory Database. If reservation succeeds for every vendor group, the orchestrator calls the Payment Service, which talks to an external payment gateway and records the transaction in the Payment Ledger Database. Only after payment is authorised does the orchestrator publish confirmation events onto the Message Broker, which the Shipping/Fulfilment Service and Notification Service consume independently, each writing to their own store and calling out to external Vendor Fulfilment APIs so each seller’s own systems learn about their new sub-order.

Notice what never happens in this walkthrough: at no point does a single request block on every downstream system, in one long synchronous chain, all the way through delivery. The synchronous, blocking part of the flow is deliberately kept as short as possible — reservation and payment only — and everything else (shipping, notification, vendor system integration) happens asynchronously after the customer already has their confirmation, which is precisely why the customer-perceived checkout latency can stay low even though a huge amount of downstream work is still to come.

05

Internal Working

The Order Orchestrator is the component doing the heaviest lifting in this entire system. Here is how a checkout actually executes, step by step, inside it.

  1. Cart Snapshot: The orchestrator asks the Cart Service for the current cart, grouped by vendor, along with a fresh price/availability check from the Catalog Service (prices can change between “add to cart” and “checkout”).
  2. Sub-Order Creation: For each vendor group, a sub-order is created with a PENDING status, linked to a single new parentOrderId.
  3. Stock Reservation (Saga Step 1): The orchestrator calls the Inventory Service for each vendor’s items, requesting a short-lived reservation (e.g., 10 minutes). If any vendor’s items are unavailable, previously reserved stock from other vendors is released (compensation), and the customer sees a clear message about which item is unavailable — not a generic failure.
  4. Payment Authorisation (Saga Step 2): Once all vendors confirm reservation, the Payment Service authorises (not yet captures) the full cart total on the customer’s payment method.
  5. Commit Phase: On successful authorisation, the orchestrator commits: stock reservations become permanent deductions, the payment is captured, and each sub-order moves to CONFIRMED.
  6. Fan-out to Vendors (Saga Step 3): An event per sub-order is published to the message broker; the Shipping/Fulfilment Service and each vendor’s own system consume these events independently and asynchronously.
  7. Parent Order Status Aggregation: As each sub-order changes status independently over the following days (packed, shipped, delivered), the parent order’s displayed status is computed as a roll-up (e.g., “3 of 4 items shipped”).
Real-life analogy

This is exactly how a wedding caterer coordinating multiple external vendors (florist, band, photographer) works: the caterer (orchestrator) confirms availability with each vendor first, only then takes the client’s deposit, and only after the deposit clears does each vendor get the formal go-ahead to proceed independently.

5.1 Choreography vs. Orchestration

There are two ways to implement a Saga. In orchestration (used above), one central coordinator service explicitly tells each step what to do next and issues compensations directly — easier to understand, trace, and debug, but the orchestrator becomes a critical, complex component. In choreography, there is no central coordinator; each service reacts to events published by the previous one and publishes its own events in turn. Choreography scales organisational ownership better (each team owns its reaction logic) but makes the end-to-end flow harder to trace and debug, especially for compensations.

Most large marketplaces use a hybrid: orchestration for the critical checkout path (cart → reservation → payment, where correctness and traceability matter most), and choreography for downstream, less time-critical fan-out (shipping updates, notifications, analytics).

OrderOrchestrator.java
// Simplified orchestrator step for checkout, showing compensation on partial failure
public class OrderOrchestrator {

    public OrderResult checkout(Cart cart, PaymentMethod paymentMethod) {
        String parentOrderId = idGenerator.newId();
        List<SubOrder> subOrders = splitByVendor(cart, parentOrderId);
        List<ReservationResult> reservations = new ArrayList<>();

        try {
            // Step 1: reserve stock per vendor, tracking what succeeded
            for (SubOrder so : subOrders) {
                ReservationResult r = inventoryClient.reserve(
                    so.getVendorId(), so.getItems(), so.getIdempotencyKey());
                if (!r.isSuccess()) {
                    compensateReservations(reservations); // undo everything reserved so far
                    return OrderResult.failed("Item unavailable from vendor: " + so.getVendorId());
                }
                reservations.add(r);
            }

            // Step 2: authorize payment for the full cart total
            PaymentResult payment = paymentClient.authorize(
                cart.getTotal(), paymentMethod, parentOrderId);
            if (!payment.isSuccess()) {
                compensateReservations(reservations);
                return OrderResult.failed("Payment authorization failed");
            }

            // Step 3: commit — convert reservations to permanent deductions, capture payment
            inventoryClient.commitAll(reservations);
            paymentClient.capture(payment.getAuthorizationId());
            orderRepository.markSubOrdersConfirmed(subOrders);
            eventPublisher.publishOrderConfirmed(parentOrderId, subOrders);

            return OrderResult.success(parentOrderId);

        } catch (Exception ex) {
            compensateReservations(reservations); // always undo on unexpected failure
            throw new CheckoutException("Checkout failed, all reservations released", ex);
        }
    }

    private void compensateReservations(List<ReservationResult> reservations) {
        for (ReservationResult r : reservations) {
            inventoryClient.release(r.getReservationId()); // compensating transaction
        }
    }
}
i
What an interviewer may ask
  • “What happens if the orchestrator itself crashes mid-saga?” — The saga state must be persisted after every step (not held only in memory), so a recovery process can resume or compensate an in-flight saga after a restart.
  • “How do you make each reservation call idempotent?” — Pass a unique idempotency key per sub-order/step, and have the Inventory Service store recent keys to detect and ignore duplicate reservation attempts.

5.2 Timeout Budgets and Retry Policy

Analogy

A relay race has one total race time, but each runner also has their own individual leg — if one runner is allowed to run forever, the whole team’s time becomes meaningless. Checkout works the same way: the whole operation has an overall time budget, and each step gets only a slice of it.

A well-designed Order Orchestrator does not simply call each downstream dependency and wait indefinitely. Instead, it works within an overall checkout timeout budget — for example, a hard ceiling of 3 seconds for the entire synchronous checkout path — and allocates a slice of that budget to each step: perhaps 800ms for inventory reservation across all vendors in parallel, 1.5 seconds for payment authorisation (payment gateways are often the slowest external dependency), and the remainder as buffer for network and serialisation overhead. If a step is about to exceed its slice, the orchestrator fails fast and triggers compensation rather than letting the customer stare at a spinner indefinitely.

Retries are applied selectively and carefully: idempotent, read-like calls (checking current price, checking stock availability) can be retried with exponential backoff and jitter on transient network errors. Non-idempotent calls without a supplied idempotency key are never blindly retried, since a retry could mean a duplicate charge or a duplicate stock deduction. This is why idempotency keys are treated as a first-class part of the API contract for every write operation in this system, not an optional nice-to-have.

5.3 Handling the “Almost Simultaneous” Race

A particularly interesting edge case worth walking through explicitly: two customers, A and B, both have the last unit of a popular item from the same vendor in their carts, and both click “Checkout” within a few milliseconds of each other. Both requests reach the Order Orchestrator nearly simultaneously and both attempt to reserve the same single unit of stock from the Inventory Service. Because the reservation call uses an atomic, conditional database update (shown in the code sample in Section 7.2), only one of the two requests can succeed — the database itself serialises the two competing updates, and whichever transaction commits first wins the unit of stock. The losing request receives an immediate, accurate “out of stock” response rather than a false success that would later have to be walked back, which is a far better experience than accepting the order and cancelling it minutes later.

06

Data Flow and Lifecycle

The sequence below traces a checkout where a customer has items from three vendors — A, B, and C — and Vendor C’s item goes out of stock a moment before the reservation call reaches it. This shows the compensation path in action.

Client API Gateway Order Orchestrator Inventory Svc Payment Svc POST /checkout (items A, B, C) Create order request Split into 3 sub-orders Reserve stock for Vendor A Reservation confirmed Reserve stock for Vendor B Reservation confirmed Reserve stock for Vendor C Out of stock! Release reservation for Vendor A Release reservation for Vendor B Reservations released Checkout failed, item unavailable 409 Conflict — Vendor C sold out
Fig 6.1 — Partial-failure and compensation flow when one of three vendors cannot fulfil the reservation.

Once a checkout succeeds, each sub-order moves through its own independent lifecycle. The state diagram below models this, including how a partially-shipped parent order can occur when three vendors ship at different times.

start CartActive CheckoutInitiated SubOrdersCreated PaymentPending PaymentFailed PaymentAuthorized VendorFulfilment PartiallyShipped FullyShipped Delivered Cancelled Refunded end
Fig 6.2 — Order lifecycle from cart to delivery, including the partially-shipped state unique to multi-vendor orders.

Notice that PartiallyShipped is a state that simply does not exist in single-vendor systems — it is a direct consequence of independent vendor fulfilment, and the parent order’s UI must clearly communicate it (e.g., “2 of 3 items shipped, 1 pending”) rather than showing a single misleading status.

07

Database Design and Sharding

Different pieces of data in this system have very different consistency and access-pattern needs, which is why a multi-vendor marketplace almost always uses polyglot persistence — different database types for different services, rather than one giant shared database.

CART DB

NoSQL (e.g., DynamoDB, MongoDB)

Carts are read/written frequently, are naturally document-shaped (a list grouped by vendor), and don’t need cross-cart transactions. Sharded by userId for even load distribution.

ORDER DB

Relational (e.g., PostgreSQL)

Orders need strong relational integrity (parent-order to sub-order foreign keys) and support for complex queries (a vendor’s order history). Sharded by parentOrderId range or hash.

INVENTORY DB

Relational or Key-Value with atomic ops

Stock counts need atomic increment/decrement operations to avoid overselling. Sharded by vendorId so hot sellers don’t bottleneck the whole system.

PAYMENT LEDGER

Relational, ACID-strict

Money movements must be append-only and auditable. This is the one place where strict ACID transactions are non-negotiable, scoped tightly to a single payment/settlement record at a time.

CATALOG DB

Search-optimised + Relational source of truth

Product search and filtering needs full-text and faceted search (Elasticsearch); the relational store remains the source of truth for price and availability, synced asynchronously to the search index.

7.1 Why Shard by Vendor for Inventory

Sharding the inventory database by vendorId (rather than by product ID or a round-robin scheme) means that a flash sale hammering one popular vendor’s stock only stresses that vendor’s shard, not the entire inventory system. It also aligns naturally with the business reality that each vendor manages only their own inventory, simplifying access control.

7.2 Avoiding Overselling: Atomic Stock Decrements

InventoryReservationService.java
// Atomic, race-condition-safe reservation using an optimistic version check
public class InventoryReservationService {

    public ReservationResult reserve(String vendorId, String skuId, int qty, String idempotencyKey) {

        // idempotency check first: if this exact request already succeeded,
        // return the cached result
        Optional<ReservationResult> existing = reservationLog.find(idempotencyKey);
        if (existing.isPresent()) return existing.get();

        // atomic conditional decrement: only succeeds if available stock is sufficient
        int updatedRows = jdbcTemplate.update(
            "UPDATE inventory SET available = available - ?, reserved = reserved + ? " +
            "WHERE vendor_id = ? AND sku_id = ? AND available >= ?",
            qty, qty, vendorId, skuId, qty
        );

        if (updatedRows == 0) {
            return ReservationResult.failed("Insufficient stock");
        }

        ReservationResult result = ReservationResult.success(
            idGenerator.newId(), Instant.now().plusSeconds(600));
        reservationLog.save(idempotencyKey, result); // store for idempotent retries
        return result;
    }
}

The WHERE available >= ? clause is the key to correctness: the database itself guarantees that the decrement only applies if enough stock exists, closing the classic race condition where two concurrent requests both “read” 1 unit of stock available and both proceed to sell it.

i
What an interviewer may ask
  • “Why not just use a distributed lock around inventory checks?” — A conditional atomic UPDATE at the database level is far cheaper and more scalable than acquiring a distributed lock (e.g., via Redis or Zookeeper) for every stock check; locks should be a last resort, not a default.
  • “How would you shard the Order DB across regions for a global marketplace?” — Typically by customer region or a geo-aware order ID prefix, keeping an order and its shopper in the same region for latency, while replicating vendor-facing data cross-region asynchronously.

7.3 A Simplified Order Schema

To make the parent-order/sub-order relationship concrete, here is a simplified relational schema for the Order Database. Notice that sub_orders carries its own independent status and vendor reference, while orders stores only aggregate, customer-facing information.

schema.sql
CREATE TABLE orders (
    id              UUID PRIMARY KEY,
    customer_id     UUID NOT NULL,
    status          VARCHAR(30) NOT NULL,   -- derived roll-up of sub-order states
    grand_total     DECIMAL(12,2) NOT NULL,
    created_at      TIMESTAMP NOT NULL
);

CREATE TABLE sub_orders (
    id              UUID PRIMARY KEY,
    order_id        UUID NOT NULL REFERENCES orders(id),
    vendor_id       UUID NOT NULL,
    status          VARCHAR(30) NOT NULL,   -- independent per-vendor lifecycle
    subtotal        DECIMAL(12,2) NOT NULL,
    shipping_fee    DECIMAL(12,2) NOT NULL,
    tax_amount      DECIMAL(12,2) NOT NULL,
    tracking_number VARCHAR(64),
    shipped_at      TIMESTAMP
);

CREATE TABLE sub_order_items (
    id              UUID PRIMARY KEY,
    sub_order_id    UUID NOT NULL REFERENCES sub_orders(id),
    sku_id          UUID NOT NULL,
    quantity        INT NOT NULL,
    unit_price      DECIMAL(12,2) NOT NULL
);

CREATE INDEX idx_suborders_vendor_status ON sub_orders(vendor_id, status);

The index on (vendor_id, status) exists because one of the most frequent queries in the whole system is a vendor asking, through their dashboard, “show me all my pending sub-orders” — a query that must stay fast even when the table holds billions of rows across every vendor on the platform.

7.4 Shard Rebalancing as Vendors Grow

Because inventory is sharded by vendorId, a small vendor and a massive vendor with a huge catalog and order volume start out on equal footing, sharing a shard with other small and mid-sized vendors. As a vendor’s volume grows, the platform’s shard-management layer can migrate that vendor’s data onto its own dedicated shard — this is a standard “hot shard splitting” operation, done as a background, zero-downtime migration, and is exactly the kind of follow-up question that comes up in senior-level interviews for this topic (“what happens when one vendor becomes 40% of your traffic?”).

08

Caching and Load Balancing

Caching in a marketplace cart system has to be applied carefully, because caching the wrong thing (like live inventory counts) can directly cause overselling. The general rule: cache things that are safe to be slightly stale, never cache the final source of truth for money or stock at commit time.

WhatWhereTTL / Invalidation
Cart contentsRedis, keyed by userIdInvalidated immediately on any cart write; used as the fast read path.
Product catalog & price displayRedis + CDN edge cacheShort TTL (seconds to low minutes); re-validated against source of truth at checkout time.
Vendor storefront pagesCDNMinutes to hours; purged on vendor updates.
Session / auth tokensRedisTTL matches token expiry.
Live inventory count used for reservationNever cached — always read from source database with atomic operationsN/A

8.1 Load Balancing Strategy

At the edge, a Layer 7 load balancer (e.g., AWS ALB, NGINX, Envoy) distributes traffic across API Gateway instances using round-robin or least-connections algorithms, combined with active health checks so that a gateway instance experiencing high latency or errors is automatically pulled out of rotation. Internally, service-to-service traffic (Gateway → Cart Service, Orchestrator → Inventory Service) typically flows through a service mesh (e.g., Istio, Linkerd), which provides load balancing, retries with backoff, and mutual TLS between services without each service reimplementing that logic.

During flash sales, a common technique is a virtual waiting room in front of the gateway for a single hot vendor’s flash-sale page — admitting shoppers at a controlled rate so the checkout path for the rest of the marketplace remains unaffected.

8.2 Preventing Cache Stampedes

Analogy

Imagine a popular restaurant that closes for exactly one minute to clean, and the entire waiting crowd rushes the door the instant it reopens — that surge can be worse than if the restaurant had just stayed steadily busy the whole time. A cache expiring all at once creates the same effect on the database behind it.

When a hot catalog entry’s cache entry expires — a product currently in a viral flash sale, for instance — thousands of concurrent requests can miss the cache at the same instant and all hammer the primary database simultaneously, a phenomenon known as a cache stampede or “thundering herd.” Two standard mitigations are used together in this system: request coalescing, where only the first request that misses the cache actually queries the database, while all concurrent requests for the same key wait on that one in-flight lookup and share its result; and staggered TTL jitter, where cache expiry times are randomised slightly (e.g., 60 seconds plus or minus a few seconds of random jitter) so that many keys don’t all expire at the exact same moment in the first place.

8.3 Vendor-Aware Rate Limiting

The API Gateway applies rate limits along two independent dimensions simultaneously: per authenticated customer (to prevent abuse of the checkout API by a single account) and per vendor (to prevent one seller’s integration — for example, a script that syncs inventory every second instead of every minute — from consuming a disproportionate share of shared gateway and downstream capacity). Vendor-level limits are typically implemented with a token-bucket algorithm, tracked in the Cache Layer, so that limit checks add only a single fast in-memory operation to each request rather than a database round trip.

09

APIs and Microservices

Each service exposes a narrow, well-defined API. A simplified contract for the checkout endpoint, as seen by the client through the API Gateway:

POST /v1/checkout
{
  "cartId": "cart_9182",
  "shippingAddressId": "addr_442",
  "paymentMethodId": "pm_882",
  "idempotencyKey": "chk_20260731_9182_a1"
}

// Response (partial success example)
{
  "parentOrderId": "ord_55291",
  "status": "CONFIRMED",
  "subOrders": [
    { "vendorId": "v_101", "status": "CONFIRMED", "estimatedShip": "2026-08-02" },
    { "vendorId": "v_204", "status": "CONFIRMED", "estimatedShip": "2026-08-03" }
  ]
}

Note the idempotencyKey in the request — this is what allows a client to safely retry a checkout call after a network timeout without risking a double charge. The API Gateway or Order Orchestrator recognises a repeated key and returns the original result instead of re-processing.

9.1 Microservice Boundaries

Services are split along business capability boundaries, not technical layers — this is the core idea behind Domain-Driven Design applied to microservices. Cart, Catalog, Pricing, Order, Inventory, Payment, Shipping, and Notification are each owned by a different team, each with its own database, each independently deployable. The API Gateway and event contracts (message schemas on the Kafka topics) are the stable interfaces that let these teams move independently without breaking each other.

i
What an interviewer may ask
  • “Should Cart and Pricing be the same service?” — Usually no; pricing/promotions logic (coupons, vendor discounts, tax) changes far more often and has different scaling characteristics than raw cart storage, so splitting them allows independent iteration.
  • “How do services communicate — REST or gRPC or events?” — A mix: synchronous REST/gRPC for read-heavy, latency-sensitive calls in the checkout critical path (reserve stock, authorise payment), and asynchronous events (Kafka) for fan-out to shipping, notifications, and analytics that don’t need to block the customer.

9.2 The Cart Service’s Own API Surface

Before checkout even happens, the Cart Service exposes a small, focused set of endpoints that the client application calls as the customer shops: POST /v1/cart/items to add an item (validated against the Catalog Service for current price and availability before being accepted), PATCH /v1/cart/items/{itemId} to change quantity, DELETE /v1/cart/items/{itemId} to remove an item, and GET /v1/cart to retrieve the current cart, already grouped by vendor with running subtotals per vendor group. Keeping this API narrow and focused purely on cart state — with pricing, promotions, and shipping estimates deliberately delegated to their own services and merged at read time — is what allows the Cart Service to stay simple, fast, and easy to scale independently of the more complex, frequently-changing business rules that surround it.

9.3 Versioning Vendor-Facing APIs

Vendor Fulfilment APIs are a special case: unlike internal service-to-service APIs, which a platform’s own teams fully control and can change quickly, vendor-facing APIs are integrated against by thousands of independent, external systems the platform does not control. This makes API versioning and long deprecation windows especially important — a breaking change rolled out carelessly can silently break order fulfilment for a large number of sellers simultaneously. Marketplaces typically version these APIs explicitly (e.g., /vendor-api/v2/sub-orders), maintain older versions in parallel for an extended, clearly communicated deprecation period, and provide sandbox environments so vendor engineering teams can test against new versions before the old one is retired.

10

Design Patterns and Anti-Patterns

PATTERN

Saga Pattern

Coordinates the multi-step, multi-vendor checkout with compensations instead of a single distributed transaction. The backbone of this entire system.

PATTERN

Circuit Breaker

Wraps calls from the Orchestrator to Inventory/Payment/Shipping; trips open if a dependency is failing, so checkout fails fast instead of hanging and cascading.

PATTERN

Bulkhead

Isolates resource pools (thread pools, connection pools) per downstream dependency, so a slow vendor-facing shipping API can’t starve resources needed for payment processing.

PATTERN

Event Sourcing (Order state)

Some marketplaces store the order as an append-only log of events (created, reserved, paid, shipped) rather than mutable rows, making the audit trail and status derivation natural.

PATTERN

CQRS

Splits the write path (checkout, order mutation) from the read path (order history, vendor dashboards), allowing each to scale and be optimised independently.

PATTERN

Strangler Fig

Used when migrating a legacy single-vendor checkout monolith to this microservices architecture incrementally, routing traffic to new services one capability at a time.

10.1 Anti-Patterns to Avoid

Anti-Pattern — Distributed 2-Phase Commit across all services

Doesn’t scale, creates tight coupling and blocking locks across independently-owned databases. Replace with the Saga pattern and explicit compensations.

Anti-Pattern — Synchronous chain-of-calls checkout

Gateway calls Cart, which calls Inventory, which calls Payment, all synchronously in one long blocking chain — one slow link stalls everything. Replace with an orchestrator that calls dependencies with timeouts, circuit breakers, and (where safe) parallelism.

Anti-Pattern — Shared database across services

Defeats the purpose of microservices; a schema change for Payment can silently break Inventory. Replace with database-per-service, integration only via well-versioned APIs and events.

Anti-Pattern — Non-idempotent payment capture

A retried request can double-charge a customer. Every state-changing call must be idempotent via a client-supplied idempotency key.

Correct Approach Summary

Saga pattern with explicit compensations, orchestrated centrally for the critical path; database-per-service with well-versioned API and event contracts; parallel dependency calls with timeouts and circuit breakers; every state-changing call idempotent via a client-supplied idempotency key.

11

Performance and Scalability

The checkout path is the most latency-sensitive and highest-stakes part of the system — a slow or failing checkout directly costs revenue. A few concrete techniques marketplaces use:

  • Parallelise independent reservation calls: If a cart has items from 3 vendors, the reservation calls to those 3 vendors’ inventory shards can run concurrently instead of sequentially, since they don’t depend on each other — this alone can cut checkout latency significantly for large multi-vendor carts.
  • Horizontal scaling of stateless services: Cart Service, API Gateway, and Order Orchestrator hold no server-side session state, so they scale horizontally behind the load balancer with simple auto-scaling rules based on CPU or request-queue depth.
  • Read replicas for catalog/pricing: Product browsing and price display (read-heavy, tolerant of slight staleness) are served from read replicas, keeping the primary database free for writes.
  • Backpressure and queueing at flash-sale peaks: Rather than letting every request hit the Inventory Service directly, requests can queue through Kafka with the Inventory Service consuming at a sustainable rate, smoothing out traffic spikes.
  • Little’s Law in capacity planning: Average number of in-flight checkout requests (L) equals arrival rate (λ) multiplied by average time in the system (W). If checkout must handle 500 requests/second and a checkout takes 400ms end-to-end, the system needs to comfortably sustain around 200 concurrent in-flight checkouts — this directly informs connection pool and thread pool sizing for the Orchestrator.
“Splitting a cart into vendor sub-orders isn’t just a data modeling choice — it’s what allows every vendor’s slice of the checkout to scale independently.”
i
What an interviewer may ask
  • “How would you handle a checkout with 20 vendors in one cart without the p99 latency exploding?” — Parallel reservation calls with a bounded thread pool, an overall checkout timeout budget, and graceful partial-failure handling rather than waiting on the slowest vendor sequentially.

11.1 A Worked Capacity Planning Example

Suppose a marketplace’s Order Orchestrator needs to sustain a peak of 2,000 checkout requests per second during a major sale event, and end-to-end checkout latency (from the gateway receiving the request to the orchestrator returning a confirmed or failed result) averages 350 milliseconds under normal load. Applying Little’s Law ($L = lambda times W$), the expected number of concurrently in-flight checkout requests is 2,000 × 0.35 = 700. This number directly drives real infrastructure decisions: the orchestrator’s thread pool (or, in a reactive/non-blocking design, the concurrency limiter) needs headroom comfortably above 700 concurrent operations, each downstream connection pool (to Inventory, Payment) needs to support that same order of concurrent calls without queueing becoming the dominant source of latency, and auto-scaling policies should trigger well before in-flight request counts approach that theoretical ceiling, not after.

This same calculation immediately reveals a risk: if the Payment Service’s external gateway dependency ever slows down from, say, 150ms average latency to 800ms during an incident, the in-flight concurrency requirement jumps proportionally — from 700 to over 1,600 concurrent in-flight requests at the same 2,000 requests/second arrival rate — which can cascade into connection pool exhaustion and a full outage if the system wasn’t provisioned with that scenario in mind. This is exactly why timeout budgets (Section 5.2) and circuit breakers exist: they cap how bad a single slow dependency is allowed to make things, rather than letting load pile up without limit.

11.2 Read/Write Splitting for the Browsing Path

The vast majority of marketplace traffic is browsing and searching, not checking out — often by a ratio of 100:1 or higher. Keeping this browsing traffic entirely separate, served from the Catalog Service’s search index and read replicas, ensures that a traffic spike in product browsing (for example, a product going viral on social media) never competes for the same database connections and capacity that the checkout critical path depends on.

12

High Availability and Reliability

Every component in the architecture diagram is deployed across multiple availability zones, with no single instance being a single point of failure. A few reliability techniques specific to this domain:

  • Saga recovery workers: A background process periodically scans for sagas stuck mid-flight (e.g., reservation succeeded but payment step never completed within a timeout window) and either resumes or compensates them — protecting against orchestrator crashes.
  • Reservation TTL as a safety net: Even if a saga is never explicitly compensated due to some failure, the reservation naturally expires and returns stock to availability, preventing permanently “stuck” inventory.
  • Multi-region payment gateway failover: Payment Service can failover to a secondary payment processor if the primary is degraded, using the same idempotency key so the customer is never double-charged.
  • Graceful degradation: If the Pricing/Promotions Service is down, checkout can still proceed without applying a coupon (with a clear message), rather than blocking the entire purchase — a good example of prioritising the critical path over a nice-to-have.
Design GoalWhat Can Go Wrong Without This
99.99% availability for the checkout critical path (roughly 52 minutes of downtime per year)A single database failure takes down checkout for every vendor on the platform
RPO (Recovery Point Objective) near zero for payment and order dataStuck sagas silently lock up inventory indefinitely
RTO (Recovery Time Objective) under a few minutes for full regional failoverA slow vendor-facing API cascades into failing unrelated vendors’ checkouts

12.1 Disaster Recovery Runbook, In Brief

Every critical database in this architecture (Order, Payment Ledger, Inventory) is configured with synchronous or near-synchronous replication to at least one standby in a different availability zone, and asynchronous replication to a standby in a different geographic region entirely. A regional outage of the primary data center triggers a documented failover runbook: DNS and load-balancer traffic shifts to the standby region, the standby databases are promoted to primary, and the Order Orchestrator and Payment Service reconnect to the newly promoted primaries — all ideally automated, tested regularly through scheduled failover drills (“game days”), rather than something engineers are attempting for the very first time during an actual outage at 3 a.m.

A specific and easy-to-miss detail for this domain: because a checkout in progress touches multiple databases (Order, Inventory, Payment), a regional failover mid-checkout must be handled by the saga recovery mechanism described above — after failover, the recovery worker scans for sagas that were in flight at the moment of the incident and either resumes them against the newly promoted databases or safely compensates them, rather than assuming every in-flight saga simply vanished along with the outage.

12.2 Chaos Engineering for Checkout

Because this system’s reliability depends heavily on how gracefully it degrades under partial failure, mature marketplace engineering teams regularly run controlled chaos experiments in production or a production-like environment — deliberately injecting latency into the Payment Service, killing random Inventory Service instances, or simulating a slow vendor fulfilment API — specifically to verify that circuit breakers trip as expected, that sagas compensate correctly, and that the customer-facing failure messages remain clear and honest rather than generic and confusing.

13

Security

A multi-vendor marketplace has a wider attack surface than a single-vendor store, because vendors themselves are semi-trusted third parties, not the platform’s own trusted backend.

  • Authentication & Authorisation: Customers authenticate via OAuth2/JWT through the Auth Service; vendors accessing their own dashboards and fulfilment APIs are authenticated separately with scoped API keys, following the principle of least privilege — a vendor’s API key can only read/write their own sub-orders, never another vendor’s data.
  • Payment data isolation: Raw card data never touches the platform’s own services directly; tokenisation via a PCI-DSS compliant payment gateway means the Payment Service only ever handles tokens, never raw card numbers.
  • Vendor sandboxing: Vendor-supplied product data (titles, descriptions, images) is treated as untrusted input, sanitised to prevent stored XSS on product pages.
  • API rate limiting per vendor: Prevents one vendor’s misbehaving integration (e.g., a buggy inventory-sync script) from overwhelming shared infrastructure that all vendors depend on.
  • Idempotency keys double as replay protection: Combined with short expiry windows, they prevent replay attacks on payment and order-mutation endpoints.
  • Fraud detection at checkout: A lightweight synchronous risk check (velocity of orders, mismatched billing/shipping geography, known bad payment instruments) runs inline before payment authorisation; deeper, slower fraud analysis runs asynchronously post-order and can trigger a hold.
i
Zero Trust Between Services

Internal service-to-service calls (Orchestrator → Payment, Orchestrator → Inventory) use mutual TLS and short-lived service identity tokens, rather than assuming that traffic originating “inside the network” is automatically trustworthy.

13.1 Regulatory & Compliance Considerations

A multi-vendor marketplace operating internationally has to satisfy several overlapping compliance regimes simultaneously. PCI-DSS governs how payment card data is handled, transmitted, and stored — the reason tokenisation through a certified payment gateway is used instead of the platform’s own services ever touching raw card numbers. GDPR (and similar regional privacy laws) governs how customer personal data is stored, and critically, how it must be shared with — and restricted from — independent vendors, who legally receive only the minimum customer information necessary to fulfil their specific sub-order (typically shipping name and address), never the customer’s full account profile or payment details. Data residency requirements in certain countries mandate that customer order data physically remain within that country’s borders, which directly shapes the regional database sharding strategy discussed earlier in Section 7.

Vendors themselves are also subject to a lighter-weight but real verification process — Know Your Business (KYB) checks during vendor onboarding, to reduce the risk of the marketplace being used for money laundering or the sale of counterfeit or prohibited goods, which is as much a security and trust concern as it is a business one.

i
What an interviewer may ask
  • “How do you prevent a malicious vendor from seeing another vendor’s sales data?” — Strict row-level authorisation scoped by vendor ID at the API layer, enforced independently of whatever the client requests, combined with audit logging of all vendor data access.
  • “How would you limit the customer data a vendor receives?” — Expose only a minimal shipping-label view (name, address, order contents) through the vendor-facing API, never the customer’s email, payment method, or full account history.
14

Monitoring, Logging and Metrics

Because a single checkout touches many services, distributed tracing is not optional — it is the only realistic way to debug “why did this specific customer’s checkout fail” across a dozen services. A trace ID is generated at the API Gateway and propagated through every downstream call (Cart, Orchestrator, Inventory, Payment, Shipping), so all logs and spans for one checkout can be reconstructed end-to-end (using tools like Jaeger, Zipkin, or a vendor APM like Datadog).

METRICS

Golden Signals

Latency, traffic, errors, and saturation tracked per service — especially checkout p50/p95/p99 latency and checkout success rate.

METRICS

Business Metrics

Cart abandonment rate, partial-checkout-failure rate by vendor, average vendors-per-cart, saga compensation rate.

METRICS

Saga Health

Number of in-flight sagas, average saga duration, stuck-saga count, compensation success rate.

METRICS

Alerting

Paging alerts on checkout success rate dropping below threshold, payment gateway error rate spikes, and inventory reservation failure spikes for any single vendor (which may indicate a stuck sync or a real stockout wave).

Structured logging (JSON logs with parentOrderId, vendorId, and traceId as consistent fields) allows support teams to instantly pull every log line related to one customer’s problematic order across every service it touched, without manually correlating timestamps.

14.1 A Concrete Tracing Example

Picture a customer support ticket: “My order says confirmed, but only 1 of 3 items shows tracking information after four days.” An engineer investigating this pulls up the trace for that specific parentOrderId and can see, laid out as a timeline, exactly which service handled each of the other two sub-orders, how long each step took, and — critically — whether the Shipping Service ever actually received the confirmation event for those two sub-orders from the Message Broker at all, or whether it received the event but the downstream call to that specific vendor’s fulfilment API failed silently. Without end-to-end tracing, this same investigation could take hours of manually grep-ing through separate log files across half a dozen services; with it, the root cause is often visible within minutes.

14.2 Per-Vendor Operational Dashboards

Beyond platform-wide metrics, large marketplaces also expose lightweight operational dashboards to vendors themselves — order volume, fulfilment SLA compliance, and cancellation rate, scoped strictly to that vendor’s own data — both as a trust-building feature for sellers and as an early-warning signal for the platform’s own risk team, since a vendor whose cancellation rate suddenly spikes is often a leading indicator of an inventory sync problem or, less benignly, a fraud pattern worth investigating.

15

Deployment and Cloud

Each microservice is packaged as a container and deployed on an orchestration platform such as Kubernetes, typically with:

  • Independent deployment pipelines per service, so a fix to the Notification Service doesn’t require redeploying the Payment Service.
  • Blue-green or canary deployments for the Order Orchestrator and Payment Service specifically, given how costly a bad deploy would be on the critical checkout path — canary release to 1–5% of traffic first, with automatic rollback on elevated error rates.
  • Infrastructure as Code (Terraform, CloudFormation) to keep environments (staging, production, multiple regions) consistent and reproducible.
  • Auto-scaling groups tuned per service based on its actual bottleneck — Cart Service on request rate, Inventory Service on database connection saturation, Order Orchestrator on in-flight saga count.
  • Multi-region active-active or active-passive deployment for global marketplaces, with the Order and Payment databases usually kept region-local per customer for latency and data-residency compliance, while catalog data is replicated globally.

15.1 Cost Optimisation at Scale

At marketplace scale, infrastructure spend becomes a meaningful line item, and a few patterns keep it under control without compromising the checkout critical path. Services with predictable, steady-state load (Notification Service, background settlement jobs) run comfortably on reserved or spot/preemptible compute capacity, since brief interruptions are tolerable there. In contrast, the Order Orchestrator and Payment Service — the components directly on the customer-facing checkout critical path — run on stable, reserved capacity where predictable performance matters more than shaving compute costs. Auto-scaling policies are tuned to scale down aggressively during off-peak overnight hours in each region and scale up well ahead of known traffic patterns (for example, pre-warming capacity a few hours before a scheduled flash sale rather than reacting purely to real-time load, since reactive auto-scaling alone can be too slow for a traffic spike that arrives in seconds).

15.2 Feature Flags for Safe Rollout

Because a bug in checkout logic can be extremely costly, changes to the Order Orchestrator’s business logic are frequently gated behind feature flags, allowing the team to enable a new checkout behaviour — say, a new tax-calculation rule — for a small percentage of vendors or regions first, observe the relevant metrics from Section 14, and only then roll it out platform-wide, with an instant kill-switch available if something looks wrong.

16

Advantages, Disadvantages and Trade-offs

ADVANTAGE

Seamless customer experience

Seamless one-cart, one-checkout customer experience regardless of vendor count.

ADVANTAGE

Independent vendor scale

Each vendor’s data and inventory remain independently owned and scaled.

ADVANTAGE

Isolated partial failures

Partial failures are isolated — one vendor’s stockout doesn’t fail the whole cart.

ADVANTAGE

Fast vendor onboarding

New vendors can be onboarded without changing core checkout logic.

ADVANTAGE

Independent service evolution

Services can be scaled, deployed, and evolved independently.

TRADE-OFF

Eventual consistency windows

Brief windows where displayed data can lag reality — a deliberate trade in exchange for scalability.

TRADE-OFF

Operational complexity

Significantly more operational complexity than a single-vendor monolith.

TRADE-OFF

Saga engineering burden

Saga compensations add engineering and testing burden not present in simple transactions.

TRADE-OFF

Debugging infrastructure investment

Cross-service debugging requires investment in tracing infrastructure.

TRADE-OFF

Financial complexity

Split payments and per-vendor tax/commission logic add real financial complexity that a single-vendor store simply never has to think about.

17

Best Practices and Common Mistakes

Best PracticeCommon Mistake It Prevents
Always attach an idempotency key to checkout and payment requestsDouble-charging a customer on client retry after a timeout
Persist saga state after every step, not just in memoryLosing track of in-flight orders when the orchestrator restarts
Use short reservation TTLs with automatic expiryStock getting permanently “stuck” reserved by an abandoned checkout
Re-validate price and availability at checkout time, not just at add-to-cartCharging a stale price or selling an item that went out of stock minutes earlier
Design the parent order status as a roll-up of sub-order statesShowing a misleading single status when vendors ship at different times
Isolate vendor API keys and rate limits per vendorOne vendor’s runaway script degrading service for every other vendor
Run circuit breakers on every downstream call from the orchestratorOne slow dependency cascading into a full checkout outage
18

Real-World Industry Examples

EXAMPLE

Amazon Marketplace

Splits a single order into “shipments” per third-party seller; famously pioneered much of the event-driven, service-oriented architecture that the industry now considers standard for this problem.

EXAMPLE

Etsy

A single cart can span dozens of independent shops; Etsy’s checkout explicitly groups shipping and totals per shop, and its engineering blog has documented moving from a monolith toward service-oriented checkout for exactly this reason.

EXAMPLE

Flipkart

India’s leading marketplace handles massive flash-sale traffic (its “Big Billion Days” sale) using queue-based admission control and aggressive horizontal scaling of cart and order services to prevent overselling under extreme concurrency.

EXAMPLE

Alibaba / Taobao

Built from the ground up as a multi-vendor platform; pioneered large-scale use of the Saga pattern (the pattern’s practical popularisation is closely associated with Alibaba’s own engineering publications) for exactly this kind of long-lived, multi-step business transaction.

EXAMPLE

Walmart Marketplace

Layers third-party sellers on top of Walmart’s own retail inventory in the same cart, requiring careful UI and backend handling to distinguish “Sold by Walmart” from “Sold by [Vendor]” items within one unified checkout.

EXAMPLE

Uber Eats / Food Delivery

A close cousin of this pattern: a single order combining dishes from multiple restaurants (“multi-restaurant ordering”) requires the same sub-order splitting and independent fulfilment tracking, but with an added real-time logistics dimension — coordinating pickup timing across kitchens that prepare food at different speeds.

A common thread across all of these production systems is that none of them started with this fully-decomposed microservices architecture on day one. Amazon, Etsy, and Flipkart all began with far simpler, more monolithic checkout systems and evolved toward the Saga-based, event-driven design described in this tutorial only once transaction volume, vendor count, and the cost of downtime made the operational complexity worth it. This is a genuinely important lesson for system design interviews: a smaller platform with a handful of vendors and modest traffic may reasonably start with a simpler, more synchronous design and evolve toward this architecture as it scales, rather than over-engineering from the very first version.

19

Frequently Asked Questions

Q1

Can the customer pay vendors directly instead of the platform splitting payment?

In principle yes, but almost no major marketplace does this — customers strongly prefer entering payment details once. The platform charges the customer once and handles vendor payout/settlement internally, usually on a delayed schedule (e.g., weekly payouts) after deducting commission.

Q2

What happens if a vendor cancels their sub-order after payment has already been captured?

The system triggers a partial refund for just that sub-order’s amount, keeps the rest of the parent order intact, and notifies the customer — this is a compensating action that runs after the main Saga, not a full rollback of the entire order.

Q3

How is shipping cost calculated across multiple vendors?

Each vendor typically defines its own shipping rules (flat rate, free-above-threshold, weight-based); the Pricing Service aggregates these per-vendor shipping costs into one combined total shown to the customer at checkout, while still tracking each vendor’s shipping charge separately for settlement.

Q4

Why not just use one big relational database with transactions for everything?

At marketplace scale, a single shared database becomes a scaling and organisational bottleneck — every team’s writes compete for the same locks, and a single schema change risks breaking unrelated features. Splitting by service ownership trades some transactional simplicity for independent scalability and team autonomy.

Q5

How do refunds work for a multi-vendor order?

Refunds are scoped to the specific sub-order (and thus the specific vendor) being returned, reversing that portion of the payment ledger entry, and independently updating that sub-order’s status — the rest of the parent order is untouched.

Q6

What if two vendors’ items need to ship together for a discount, like free combined shipping?

This is handled as a pricing-time promotion rule evaluated by the Pricing Service across the full cart, not a fulfilment-time change — the discount is calculated and applied to the totals at checkout, but each vendor still ships and is tracked independently afterward; the “combined shipping” is a price benefit to the customer, not a change to how fulfilment actually happens physically.

Q7

Can the Order Orchestrator become a bottleneck at very high scale?

Yes, which is why it’s designed to be stateless and horizontally scalable, with saga state persisted externally (in the Order Database) rather than held in the orchestrator’s own memory — any orchestrator instance can pick up and continue any saga, and the fleet scales out behind the load balancer like any other stateless service.

Q8

How is this different from a simple shopping cart with a “seller” field on each item?

A simple seller field is a display-only distinction; it doesn’t solve the actual hard problems of split payments, independent inventory ownership, per-vendor fulfilment tracking, or partial-failure handling. The architecture in this tutorial is what’s required once those problems become real, not just cosmetic.

Q9

Do all sub-orders have to use the same shipping address?

Typically yes for a standard checkout flow — one shipping address for the whole cart, since it maps to one physical customer location — but some marketplaces allow per-item shipping addresses (for gifting scenarios, for example), which simply becomes another attribute carried on each sub-order rather than a change to the underlying architecture.

20

Summary and Key Takeaways

The Core Idea

A multi-vendor marketplace cart and checkout system presents the customer with one seamless experience — one cart, one checkout — while internally splitting that single action into independent, per-vendor sub-orders coordinated through the Saga pattern. The Load Balancer and API Gateway form the single entry point for every request; the Order Orchestrator is the brain that reserves stock, authorises payment, and fans work out to independent vendor fulfilment, with compensating transactions ready at every step to gracefully handle partial failure.

  • Split the cart and order into vendor-scoped sub-units from the very beginning, not as an afterthought.
  • Use the Saga pattern with explicit compensations — never attempt a single distributed transaction across services.
  • Make every state-changing operation idempotent using client-supplied idempotency keys.
  • Reserve inventory with a TTL rather than deducting it permanently before payment succeeds.
  • Treat the parent order’s status as a derived roll-up of independent sub-order states.
  • Invest early in distributed tracing — debugging across a dozen services without it is extremely painful.
  • Accept eventual consistency as the deliberate, correct trade-off for scalability and vendor independence.
Closing principle

A great multi-vendor marketplace checkout is judged not by how many vendors sit behind a single cart, but by how invisibly the customer moves through one clean checkout while, underneath, each vendor’s slice of the order is reserved, paid, shipped, and settled independently — with no promise made to the customer that any single vendor’s failure alone can break.