Handling Supplier Inventory Feed Delays and Failures (Preventing Overselling) System Design

Handling Supplier Inventory Feed Delays and Failures (Preventing Overselling) System Design

System Design: Handling Supplier Inventory Feed Delays and Failures

How to design a resilient inventory pipeline that stops customers from buying items you don’t actually have — circuit breakers, safety stock buffers, reservation systems, reconciliation jobs, and everything else needed to make overselling nearly impossible even when suppliers misbehave.

01

Introduction

Imagine you run an online store. You do not keep every product in your own warehouse. Instead, hundreds of suppliers send you a list of what they have in stock, and your website shows that stock to customers. This list is called an inventory feed. It usually arrives through an API — a way for two computer systems to talk to each other automatically, without a human typing anything.

Now imagine one supplier’s system goes down for two hours, or their API starts sending data very slowly, or it silently stops sending updates altogether. Your website still shows their products as “In Stock” because it has no new information telling it otherwise. Customers keep ordering. You keep accepting money. Then the feed comes back, and you discover you have sold 40 units of a product that only had 5 units left three hours ago. You now must cancel orders, refund money, apologise to customers, and possibly lose them forever.

This tutorial designs a complete system that prevents this from happening. We will build it piece by piece, in simple language, the same way you would explain it to a smart 10-year-old, and then go deep enough that a senior engineer or system design interviewer would be satisfied. Every major decision will include a real analogy, a beginner example, a software example, and how big companies like Amazon, Walmart, and Flipkart actually solve this problem.

💡
What You Will Build by the End

A supplier inventory ingestion pipeline with a circuit breaker for failing feeds, a safety stock buffer, a reconciliation engine, an inventory reservation system with optimistic locking, and full monitoring — all designed to make overselling nearly impossible even when suppliers misbehave.

02

History and Evolution of the Inventory Sync Problem

Before we design anything, let’s see how this problem grew alongside the internet economy — it did not always exist in this shape.

1

Pre-Internet — Walking the Aisles

Before the internet, a shop owner counted stock by walking around the store. If something ran out, a human noticed and stopped selling it. There was no “feed” because there was no separation between the seller and the source of truth — they were the same person, standing in the same room as the goods.

2

1990s — Single-Warehouse E-Commerce

When e-commerce began in the 1990s, most online stores still owned their own warehouse. Inventory counts lived in one database, and the website read directly from that database. This was simple, but it did not scale — a single retailer cannot stock everything, and customers want huge selection.

3

2000s — Marketplaces and Drop-Shipping

The 2000s brought the rise of the marketplace model and drop-shipping. Platforms like eBay, and later Amazon Marketplace, Flipkart, and Shopify, allowed thousands of independent suppliers to list products without the platform ever touching the physical goods. Stock information now lived on someone else’s computer, and had to travel across the internet to reach the platform’s database. The moment that “travel” step was introduced, delay and failure became possible for the first time.

4

2010s — API Integrations at Scale

As API-based integrations became the standard way businesses connected systems, the problem grew larger. A platform might integrate with 500 or 5,000 suppliers, each with different reliability, response times, and data formats. Some suppliers push updates to you (webhooks); others expect you to pull updates from them (polling). Any single one of these connections breaking, even for an hour, can cause an oversell.

5

2010s (continued) — The CAP Theorem Lesson

This same decade saw a parallel lesson coming out of distributed systems research more broadly. Engineers building large-scale systems at Amazon, Google, and eBay discovered that any system spread across multiple machines connected by an unreliable network cannot guarantee three things at once: perfect consistency, constant availability, and tolerance of network problems. This idea, formalised as the CAP theorem (covered in detail in Chapter 4.8), gave engineers the vocabulary to explain why “just always show the correct stock number instantly” is not actually achievable across a network boundary — you must consciously choose which guarantee to relax.

6

Late 2010s — Kafka and Event-Driven Decoupling

By the late 2010s and into the 2020s, message queues like Apache Kafka became the standard backbone for exactly this kind of problem — they let a fast-changing, unreliable data source (a supplier) be decoupled from a system that needs to stay fast and available (a storefront), with a durable buffer in between that can absorb bursts, outages, and retries without losing data.

7

Today — A Solved Toolkit, Not a Solved Problem

Today, in 2026, this is considered a solved problem at a system-design level — not because failures no longer happen, but because engineers have agreed on a standard toolkit (circuit breakers, event queues, safety buffers, reconciliation jobs, optimistic concurrency control) that keeps failures from turning into broken customer promises. This tutorial teaches you that exact toolkit, building it from first principles.

03

The Problem and Business Motivation

3.1 What Exactly Is “Overselling”?

Overselling happens when your system accepts and confirms more orders for a product than you actually have available to ship. If a supplier has 5 units of a lamp left, and your system sells 8 because it thought there were still 20 in stock, you have oversold by 3 units.

Real-life analogy — imagine a movie theatre that sells tickets from a paper chart in the lobby, but also sells tickets online from a chart that hasn’t been updated in an hour. If 10 seats were bought in the lobby, but the website still shows those 10 seats as available, the website will “sell” seats that do not exist. Someone will show up with a valid ticket and no seat to sit in.

3.2 Why Does the Supplier Feed Break in the First Place?

  • Network failures — the internet connection between you and the supplier’s server drops.
  • Supplier server downtime — their server crashes, is under maintenance, or is overloaded.
  • Slow responses (latency spikes) — the API technically works but takes 30 seconds instead of 200 milliseconds, causing timeouts.
  • Silent data corruption — the feed sends a response, but the numbers are wrong, incomplete, or in the wrong format.
  • Rate limiting — the supplier blocks you temporarily because you called their API too many times.
  • Authentication expiry — an API key or token expires and nobody renewed it in time.
  • Partial batch failures — out of 10,000 products in a feed update, 200 fail validation and are silently dropped, leaving stale stock numbers for just those 200.

3.3 Why This Matters for the Business

ConsequenceImpact
Order cancellationCustomer trust drops; refund processing costs money and support time.
Negative reviews“Ordered but they cancelled” is one of the most damaging review types in e-commerce.
Marketplace penaltiesPlatforms like Amazon can suspend or demote sellers who oversell repeatedly.
Wasted marketing spendYou paid to advertise a product that could not actually be delivered.
Legal or compliance riskIn some regions, repeatedly selling unavailable stock without disclosure can trigger consumer-protection complaints.

3.4 Quantifying the Risk

It helps to put numbers on this problem instead of treating it abstractly. Suppose a product sells an average of 2 units per minute during a promotion, and a supplier’s feed has been stale for 20 minutes because of an outage. If the system naively kept selling based on the last known number, it could sell up to 40 units that may not exist. If the true remaining stock was only 15 units, that is a potential oversell of 25 units — 25 real customers with confirmed orders that cannot be fulfilled. This simple multiplication (sales velocity multiplied by staleness duration) is exactly the calculation the safety stock buffer in Chapter 4.4 and Chapter 8.2 is designed to absorb automatically.

i
What an Interviewer May Ask

“Walk me through why a naive architecture — where the website reads stock directly from the supplier’s live API on every page view — is a bad idea.” A strong answer: it creates a hard dependency between your checkout flow and a third party you do not control. If the supplier is slow, your site becomes slow. If the supplier is down, your product pages break. It also does not scale — you cannot call an external API on every single page view from every user. The correct approach is to maintain your own local, cached copy of stock, kept fresh by an asynchronous ingestion pipeline, decoupled from the read path that customers hit.

04

Core Concepts You Must Know

Before drawing a single architectural box, let’s put names to the small vocabulary that shows up in every layer of this design.

4.1 Source of Truth vs. Cached Copy

The supplier’s own database is the real, authoritative “source of truth” for how much stock exists. Your platform keeps a cached copy of that number so that your website is fast and does not depend on the supplier being online at that exact millisecond. The entire design challenge of this tutorial is: how do you keep that cached copy trustworthy, and what do you do when you cannot refresh it?

4.2 Push vs. Pull Integration

  • Pull (polling): Your system calls the supplier’s API every few minutes and asks, “What’s your stock right now?” Simple to build, but wastes calls when nothing changed, and there is always a gap between polls.
  • Push (webhook): The supplier’s system calls your API the instant something changes. Much fresher data, but it depends on the supplier building and maintaining that webhook reliably.
Beginner example — polling is like calling your friend every 10 minutes to ask if dinner is ready. A webhook is like your friend calling you the moment dinner is ready. Polling wastes effort; webhooks require your friend to remember to call.

4.3 Circuit Breaker

A circuit breaker is a safety switch, borrowed from electrical engineering. In electrical wiring, if too much current flows, a fuse “breaks” the circuit before wires melt or a fire starts. In software, if a dependency (like a supplier’s API) starts failing repeatedly, the circuit breaker “opens” and your system stops calling it for a while, protecting itself from cascading failure and wasted resources, and instead falls back to a safe default behaviour.

4.4 Safety Stock Buffer

A safety stock buffer is a small quantity you deliberately hide from customers as a cushion against uncertainty. If a supplier reports 20 units, you might only show 17 as purchasable, keeping 3 as a buffer against feed staleness or counting errors.

4.5 Idempotency

Idempotency means doing an operation multiple times has the same effect as doing it once. This matters because networks can deliver the same feed update twice (duplicate messages), and if your system is not idempotent, it might subtract stock twice for the same event, creating wrong numbers.

4.6 Optimistic Locking

Optimistic locking is a way to safely update a shared number (like stock count) when many people might be trying to update it at the same time, without locking the whole database row for a long time. It checks “has this value changed since I last read it?” before committing a change, and retries if there is a conflict.

4.7 Eventual Consistency

Eventual consistency means that after an update happens, all parts of the system will reflect it — but not necessarily at the exact same instant. Your cache might show old stock for a few seconds after the database has the new number. This is normal and acceptable in most e-commerce systems, as long as the gap is small and reservation logic protects the checkout path.

4.8 CAP Theorem, Applied to Inventory

The CAP theorem states that a distributed system can only fully guarantee two of the following three at the same time: Consistency (every read sees the latest write), Availability (every request gets a response, even during failures), and Partition tolerance (the system keeps working even when parts of the network cannot talk to each other). Because a real network can and will experience partitions, the real choice in practice is between consistency and availability during a partition.

This inventory system deliberately chooses availability over strict consistency for the read path: when the supplier connection is partitioned (down), the product page still loads and still shows a number, rather than freezing or erroring out. It compensates for the resulting consistency risk using the safety buffer, reservation system, and reconciliation job, rather than trying to achieve impossible perfect real-time consistency across an unreliable third-party network.

4.9 Partitioning and Sharding

Partitioning means splitting data or workload into independent chunks so they can be processed in parallel and so a problem in one chunk does not affect the others. In this system, supplier ingestion work is partitioned by supplierId — every supplier is deterministically assigned (using consistent hashing) to one ingestion worker. As the number of suppliers grows, you add more workers and the hashing spreads suppliers across them automatically, without needing to redesign anything.

Beginner example — imagine sorting a huge pile of mail by the first letter of the recipient’s last name, and giving each range of letters (A to F, G to M, and so on) to a different postal worker. Each worker only deals with their slice, so the whole sorting job finishes faster and one worker having a bad day only slows down their own slice, not everyone else’s mail.

4.10 Replication and Consensus

Replication means keeping multiple copies of the same data on different machines, so losing one machine does not lose the data. The inventory database uses a primary-replica setup: writes go to a primary node, and that data is copied (replicated) to one or more standby replicas. If the primary fails, a consensus protocol (commonly Raft, used internally by many managed database and coordination systems) is used to have the healthy replicas agree on which one becomes the new primary, avoiding a scenario where two nodes both believe they are in charge and accept conflicting writes — a dangerous condition known as “split brain.”

4.11 Concurrency Control

Concurrency control is how a system safely handles many operations trying to touch the same data at the same time. There are two broad strategies: pessimistic locking, where you lock a row before reading it so nobody else can touch it until you are done (safe, but can create bottlenecks under high traffic), and optimistic locking (introduced in Chapter 4.6), where you do not lock anything up front, but detect conflicts at write time and retry. For a high-traffic flash sale where thousands of customers might try to buy the last few units of a product within the same second, optimistic locking generally performs better because it does not force requests to queue up waiting for a lock; it only pays a retry cost on the (usually rare) occasions where two requests genuinely collide.

05

Architecture and Components

Below is the full high-level architecture. Every major infrastructure component — the load balancer, the API gateway, the circuit breaker, the message queue, the cache, and the database — is shown as its own labelled box so you can see exactly where each piece sits in the request path.

5.1 Component-by-Component Explanation

Load Balancer

Sits in front of all your API servers and spreads incoming traffic across many machines so no single server gets overwhelmed. In production this is typically an AWS Application Load Balancer, NGINX, or HAProxy, doing Layer 7 (HTTP-aware) routing with health checks so it automatically stops sending traffic to a server that has crashed.

Analogy — a load balancer is like the host at a restaurant entrance who looks at which waiters are free and seats new customers with the least-busy waiter, instead of everyone crowding one table.

API Gateway

The single front door for all API traffic. It handles authentication (checking who is calling), rate limiting (stopping abuse), request routing (sending “get product” requests to the Inventory Service and “place order” requests to the Order Service), and can also do request validation and basic logging before traffic ever reaches your business logic.

Circuit Breaker (Feed Health Guard)

Wraps every outbound call to a supplier’s API. It tracks recent success and failure rates per supplier. If a supplier’s failure rate crosses a threshold (for example, more than 50% of calls failing in the last minute), the circuit “opens,” meaning the ingestion service stops calling that supplier for a cooldown period and instead uses fallback logic, described in Chapter 8.

Supplier Feed Ingestion Service

A dedicated microservice whose only job is to fetch or receive inventory updates from suppliers, validate them, and normalise them into your platform’s internal format before publishing them onward. Keeping this separate from the core Inventory Service means a badly-behaved supplier cannot directly slow down or crash the system customers interact with.

Message Queue (Kafka)

A durable, ordered pipe that decouples “receiving a feed update” from “applying a feed update.” The ingestion service publishes an event like “Product X now has 12 units,” and the Inventory Service consumes it whenever it is ready. This means a burst of 50,000 updates from a supplier does not overwhelm the database directly — the queue absorbs the burst and the consumer processes at a controlled pace.

Cache Layer (Redis)

Holds the current stock count in memory so that when a customer loads a product page, the answer comes back in single-digit milliseconds instead of querying the database every time. The cache is refreshed whenever the underlying stock changes in the database.

Inventory Database (PostgreSQL)

The durable source of truth on your side (as opposed to the supplier’s own database, which is the ultimate source of truth). Stores current stock counts, reservation records, and version numbers used for optimistic locking.

Safety Stock Buffer Service

A small rules engine that decides how much of the reported stock to actually expose to customers, based on supplier reliability, feed freshness, and product risk category (fast-moving items get a bigger buffer than slow-moving items).

Reconciliation Service

Runs on a schedule (for example every 15 minutes) and independently re-verifies stock for high-risk or high-velocity products directly against the supplier, catching any drift the real-time pipeline may have missed.

Monitoring and Alerting Stack

Watches feed freshness, circuit breaker state, error rates, and oversell incidents, and pages an on-call engineer when something crosses a dangerous threshold.

i
What an Interviewer May Ask

“Why not have the Order Service call the supplier directly at checkout time to confirm stock before accepting the order?” That would make checkout latency depend on an external, unreliable system, and if the supplier’s API is down, customers could not check out at all — even for products from healthy suppliers. Instead, we keep an internal, fast, always-available cached view of stock, refreshed asynchronously, and we accept a small, well-controlled risk window in exchange for speed and availability. We manage that risk with safety buffers and reservations, not with a synchronous external call on the critical path.

06

Internal Working: How the Pieces Cooperate

Let’s trace what happens, step by step, in the healthy (non-failure) case, before we look at what happens when things break.

1

Poll or Receive

The Supplier Feed Ingestion Service either polls the supplier’s API on a schedule, or receives a webhook push, depending on what that supplier supports.

2

Circuit Breaker Check

Every outbound call passes through the Circuit Breaker, which records whether the call succeeded, failed, or timed out.

3

Validate the Payload

On success, the raw payload is validated: are the required fields present? Are quantities non-negative numbers? Is the product ID recognised?

4

Publish to the Queue

Valid records are normalised into a standard internal event shape and published to the Message Queue.

5

Apply with Optimistic Locking

The Inventory Service consumes these events, applies the Safety Stock Buffer rules, and writes the new stock value to PostgreSQL using optimistic locking to avoid clobbering concurrent updates.

6

Refresh the Cache

After a successful database write, the Inventory Service updates the Redis cache so future reads are fast and fresh.

7

Record Freshness

The Monitoring stack records feed freshness (“last successful update for Supplier X was 40 seconds ago”) for every supplier.

6.1 Sequence Diagram of the Normal Flow

6.2 Handling Out-of-Order Events

Networks do not guarantee that messages arrive in the order they were sent. A supplier might publish “stock is now 10” followed quickly by “stock is now 8,” but due to network timing, your Inventory Service could receive the “8” event before the “10” event. If you apply them in the wrong order, you would end up incorrectly showing 10 units instead of the correct, more recent 8.

To prevent this, every inventory event carries a monotonically increasing sequence number or source timestamp from the supplier’s own system. The consumer compares the incoming event’s sequence number against the last-applied sequence number stored for that product. If the incoming event is older than what has already been applied, it is discarded as a stale, out-of-order duplicate rather than being applied and silently corrupting the stock count.

InventorySequenceGuard.java — drop stale, out-of-order events by comparing sequence numbers.
public void applyIfNewer(InventoryUpdateEvent event) {
    InventoryRecord record = inventoryRepo.findByProductId(event.getProductId());

    if (record != null && event.getSequenceNumber() <= record.getLastAppliedSequence()) {
        return; // stale or duplicate event, safely ignore
    }

    record.setSupplierReportedQuantity(event.getQuantity());
    record.setLastAppliedSequence(event.getSequenceNumber());
    inventoryRepo.save(record);
}

6.3 Java Example: An Idempotent Event Consumer

This consumer checks a unique event ID before applying an update, so replayed or duplicate messages from the queue never double-count stock changes.

InventoryEventConsumer.java — idempotent Kafka consumer that guards against duplicate deliveries.
@Service
public class InventoryEventConsumer {

    private final ProcessedEventRepository processedEvents;
    private final InventoryRepository inventoryRepo;

    public InventoryEventConsumer(ProcessedEventRepository processedEvents,
                                   InventoryRepository inventoryRepo) {
        this.processedEvents = processedEvents;
        this.inventoryRepo = inventoryRepo;
    }

    @KafkaListener(topics = "inventory-updates")
    public void handle(InventoryUpdateEvent event) {
        // Idempotency check: skip if we already processed this exact event
        if (processedEvents.existsByEventId(event.getEventId())) {
            return;
        }

        InventoryRecord record = inventoryRepo.findByProductId(event.getProductId());
        if (record == null) {
            record = new InventoryRecord(event.getProductId());
        }

        record.setSupplierReportedQuantity(event.getQuantity());
        record.setLastFeedTimestamp(event.getTimestamp());
        record.setFeedSource(event.getSupplierId());

        inventoryRepo.save(record);
        processedEvents.markProcessed(event.getEventId());
    }
}
07

Data Flow and Lifecycle of a Supplier Feed Update

Every inventory record in the system moves through a predictable lifecycle. Understanding this lifecycle is the key to designing correct failure handling.

StageDescription
1. FreshFeed update received within the expected freshness window (for example, under 5 minutes old). Full trust; buffer rules are minimal.
2. AgingNo new update for longer than expected, but still within a tolerable window. System begins applying a larger safety buffer.
3. StaleUpdate is significantly overdue. Circuit breaker for that supplier likely open. System may hide the product or show “limited stock” messaging.
4. RecoveredFeed resumes; new data arrives and is reconciled against any orders placed during the stale window.
Software example — think of stock freshness like a carton of milk with a “best before” sticker, except the sticker is a timestamp your system checks constantly. Fresh milk (recent data) gets sold normally. As it approaches its date (aging), a shop might discount it or watch it closely. Past the date (stale), it comes off the shelf until someone checks it is still good.

7.1 Freshness-Aware Read Path

When a customer views a product page or tries to add an item to their cart, the Inventory Service does not just return a raw number — it returns a number adjusted for freshness and buffer rules. This keeps the risk-management logic centralised in one place instead of scattered across every caller.

InventoryReadService.java — the read path applies freshness and buffer rules before returning a purchasable quantity.
public int getPurchasableQuantity(String productId) {
    InventoryRecord record = cache.get(productId);
    long ageSeconds = Duration.between(record.getLastFeedTimestamp(), Instant.now()).getSeconds();

    if (ageSeconds > staleThresholdSeconds) {
        return 0; // treat as unavailable until reconciled
    }

    int buffer = bufferPolicy.calculateBuffer(record, ageSeconds);
    return Math.max(0, record.getSupplierReportedQuantity() - buffer);
}

7.2 Handling Partial Batch Failures

Suppliers often send updates as a single large batch — for example, 10,000 products in one API response. A common and dangerous failure mode is a partial batch failure: 9,800 records are valid and 200 fail validation (bad format, missing fields, impossible values like negative stock). A naive system either rejects the entire batch (unnecessarily stalling 9,800 good updates) or accepts everything blindly (letting 200 bad records corrupt the data).

The correct approach is row-level validation with partial acceptance: valid records are processed and published normally, while invalid records are routed to a separate “dead letter” queue for manual or automated review, and the affected products are individually flagged as having a stale or untrusted feed — exactly as if that one supplier connection had failed just for those specific items. This keeps a formatting bug in a tiny fraction of a feed from blocking updates to everything else.

08

Handling Supplier Feed Delay or Failure

This is the heart of the system. When the supplier’s feed is delayed or fails outright, we need a clear, automatic decision process rather than customers silently being allowed to buy phantom stock.

8.1 The Circuit Breaker State Machine

The circuit breaker tracks three states:

  • Closed: Everything is healthy. Calls to the supplier flow through normally.
  • Open: Too many recent failures. The breaker stops calling the supplier for a cooldown period and immediately triggers fallback behaviour.
  • Half-Open: After the cooldown, the breaker allows a small number of test calls through. If they succeed, it closes again; if they fail, it re-opens.

8.2 What “Fallback” Actually Means Here

Fallback does not mean “keep selling as normal and hope for the best.” It means:

  1. Freeze the last known good stock snapshot for that supplier — do not trust any newer, unverified number.
  2. Apply an aggressive safety buffer that grows the longer the feed stays down (for example, reduce shown quantity by an increasing percentage every 10 minutes of staleness).
  3. Once staleness passes a hard limit (say 30 minutes for fast-moving products, longer for slow movers), mark the product as “Temporarily Unavailable” rather than guessing.
  4. Continue accepting orders only up to the buffered quantity, and place any accepted order into a “provisional” state that can be automatically cancelled with an apology and refund if reconciliation later shows it should not have been accepted — this should be rare if buffers are sized correctly.

8.3 Java Example: Circuit Breaker Using Resilience4j

SupplierCircuitBreaker.java — per-supplier Resilience4j breaker with a slow-call threshold and cooldown.
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
        .failureRateThreshold(50)                            // open if 50% of calls fail
        .slowCallRateThreshold(50)
        .slowCallDurationThreshold(Duration.ofSeconds(3))
        .waitDurationInOpenState(Duration.ofMinutes(2))
        .permittedNumberOfCallsInHalfOpenState(5)
        .slidingWindowSize(20)
        .build();

CircuitBreaker breaker = CircuitBreaker.of("supplier-" + supplierId, config);

Supplier<InventorySnapshot> decoratedCall = CircuitBreaker
        .decorateSupplier(breaker, () -> supplierClient.fetchInventory(supplierId));

Try<InventorySnapshot> result = Try.ofSupplier(decoratedCall)
        .recover(throwable -> fallbackService.getLastKnownGoodSnapshot(supplierId));

InventorySnapshot snapshot = result.get();
i
What an Interviewer May Ask

“Why use a circuit breaker instead of just retrying the API call a few times?” Blind retries against an already-struggling supplier make things worse — they add more load to a system that is already failing, and they keep your own resources (threads, connections) tied up waiting. A circuit breaker recognises a sustained failure pattern and stops trying for a while, protecting both sides, while a retry-with-backoff strategy handles the smaller, transient blips within the breaker’s closed state.

8.4 Timeout and Retry Strategy

Every outbound call to a supplier must have an aggressive timeout (for example 3 seconds) — never wait indefinitely. Retries should use exponential backoff with jitter (waiting 1s, then 2s, then 4s, with some randomness added) so that many services retrying at once do not all hammer the supplier at the exact same moment, which is called a “thundering herd.”

8.5 Distinguishing Between the Three Failure Types

Not all failures should be treated the same way, because they carry different information:

Failure typeWhat it meansCorrect response
Hard failure (connection refused, 5xx error)Supplier system is clearly down or broken.Trip circuit breaker quickly; treat data as unknown, not zero.
Timeout (no response within limit)Supplier may be overloaded or the network is congested; the true state is unknown.Count toward circuit breaker failure rate, but retry with backoff before giving up.
Valid response, zero stockSupplier explicitly confirmed there is no stock.Trust it immediately — this is real, high-confidence information, not a failure at all.

Confusing “the supplier told us zero” with “we could not reach the supplier” is a common and costly bug — the first should immediately hide the product, while the second should trigger the fallback and buffer logic rather than jumping straight to hiding it, since the item may well still be in stock.

8.6 The Exponential Backoff Formula

A standard exponential backoff with jitter calculates the wait time before the next retry as:

backoff.pseudo — the retry wait time in one line.
waitTime = min(maxWait, baseWait * (2 ^ attemptNumber)) + random(0, jitterMs)

For example, with a baseWait of 500 milliseconds and a maxWait cap of 30 seconds, the first retry waits around half a second, the second around a second, the third around two seconds, and so on, doubling each time until it hits the cap. The random jitter added on top prevents many failed requests from all retrying at exactly the same moment and overwhelming the supplier the instant it comes back online.

09

Reconciliation and Preventing Overselling at Checkout

Even with buffers and circuit breakers, the real defence against overselling happens at the exact moment of checkout, using inventory reservation combined with optimistic locking, plus a background reconciliation job that catches anything that slipped through.

9.1 State Diagram of an Order Versus Stock

9.2 Java Example: Optimistic Locking on Stock Reservation

ReservationService.java — JPA @Version turns a concurrent race for the last unit into a safe, retryable failure.
@Entity
public class InventoryRecord {
    @Id
    private String productId;
    private int availableQuantity;

    @Version
    private long version; // JPA uses this for optimistic locking automatically
}

@Service
public class ReservationService {

    private final InventoryRepository repo;

    @Retryable(value = OptimisticLockException.class, maxAttempts = 3)
    @Transactional
    public boolean reserveStock(String productId, int requestedQty) {
        InventoryRecord record = repo.findByProductId(productId);

        if (record.getAvailableQuantity() < requestedQty) {
            return false; // not enough stock, fail fast
        }

        record.setAvailableQuantity(record.getAvailableQuantity() - requestedQty);
        repo.save(record); // JPA checks the version column; throws if it changed underneath us
        return true;
    }
}

The @Version field means that if two customers try to buy the last unit at the same time, only one write will succeed. The database rejects the second write because the version number no longer matches what that request originally read, forcing a retry that will correctly see zero stock remaining.

9.3 Reservation Timeout

When a customer adds an item to their cart or begins checkout, the system should place a short-lived reservation (for example, 10 minutes) rather than permanently deducting stock. If the customer abandons checkout, the reservation automatically expires and the stock returns to the available pool. This prevents “phantom” stock lockups where abandoned carts quietly hoard inventory.

9.4 The Reconciliation Job

On a schedule, a background job re-fetches authoritative stock numbers directly from each supplier for high-risk products (fast sellers, low-stock items, or products flagged during a recent feed outage) and compares them against what the platform currently believes. Any mismatch triggers an automatic correction and, if needed, a review of any orders placed during the mismatch window.

9.5 What Happens When the Reconciliation Job and the Real-Time Pipeline Disagree

Occasionally, the reconciliation job’s fresh call to the supplier will disagree with what the real-time pipeline currently believes. In this case, the reconciliation result — being a fresh, directly-verified read — always wins and overwrites the cached value, since it is by definition more current. The size and direction of the disagreement is also logged as a data point that feeds back into that supplier’s reliability score, which in turn affects how large a safety buffer future orders for that supplier’s products will use. A supplier that frequently disagrees with reconciliation earns a larger, more conservative buffer automatically, without a human needing to notice and manually adjust it.

i
What an Interviewer May Ask

“Why do you need both a real-time pipeline and a separate reconciliation job? Isn’t that redundant?” The real-time pipeline optimises for speed and low overhead, which means it trusts incoming data quickly. The reconciliation job is a slower, independent safety net that catches drift caused by dropped messages, silent bugs, clock skew, or partial feed failures that did not trip the circuit breaker. In distributed systems, a single mechanism is rarely enough — defence in depth matters, especially when money and customer trust are on the line.

10

Databases, Caching and Load Balancing

10.1 Choosing the Database

PostgreSQL (or another strong relational database like MySQL) is the right default for the inventory table because stock counts need strong consistency guarantees, row-level locking, and transactions — features relational databases provide natively. A NoSQL document store could work for the raw supplier feed logs (which are write-heavy and do not need transactions), but the authoritative stock count itself belongs in a system that supports ACID transactions.

10.2 Caching Strategy

  • Cache-aside pattern: The Inventory Service reads from Redis first; on a cache miss, it reads from PostgreSQL and populates the cache.
  • Write-through updates: Whenever stock changes in the database, the cache is updated in the same operation, so reads never see very stale data.
  • Short TTL as a backstop: Even with write-through updates, set a short time-to-live (for example, 60 seconds) on cached stock values, so any missed invalidation self-heals quickly.

10.3 Load Balancing Considerations

The load balancer in front of the API layer should use health checks that go beyond “is the process running” — it should check that the service can actually reach its database and cache, so a server with a broken dependency is pulled out of rotation automatically. For the ingestion pipeline specifically, load balancing supplier polling across multiple ingestion worker instances (using consistent hashing by supplier ID) prevents one slow supplier from starving the workers handling everyone else.

10.4 Database Sharding at Scale

For a platform with millions of products, a single PostgreSQL instance may eventually become a bottleneck even with read replicas. At that scale, the inventory table can be sharded — split across multiple database instances, commonly by a hash of the product ID or by supplier ID, so each shard only needs to handle a fraction of total traffic. This is the same partitioning idea from Chapter 4.9, applied directly to storage rather than to processing workers.

10.5 Data Structures Behind the Scenes

A few specific data structures make this design efficient in practice:

data structure

Hash Maps

Back the Redis cache itself, giving O(1) average-time lookups for “what is the stock of product X” — essential for keeping product pages fast under heavy traffic.

data structure

Sliding Window Counter

A small circular buffer of recent call outcomes powers the circuit breaker’s failure-rate calculation, letting it answer “what percentage of the last 20 calls failed” in constant time without recomputing from scratch on every call.

data structure

Min-Heap on Reservation Expiry

A time-ordered index on reservation expiry times lets the reservation-expiry job efficiently find “which reservations have expired and need to be released” without scanning every reservation in the system.

11

APIs and Microservices

11.1 Service Boundaries

Keeping the Feed Ingestion Service, Inventory Service, and Order Service as separate microservices (rather than one giant monolith) means a bad supplier integration cannot crash checkout, and each service can be scaled independently based on its own load pattern — ingestion load depends on supplier count, while order load depends on customer traffic.

11.2 Example Internal API Contract

GET /internal/inventory/{productId} — freshness-aware read.
GET /internal/inventory/{productId}
Response 200:
{
  "productId":          "SKU-88231",
  "availableQuantity":  14,
  "supplierId":         "SUP-4471",
  "lastFeedTimestamp":  "2026-08-03T09:14:02Z",
  "freshnessState":     "FRESH",
  "bufferApplied":      2
}
POST /internal/inventory/reserve — short-lived reservation with a 409 on stock-out.
POST /internal/inventory/reserve
Request:
{
  "productId": "SKU-88231",
  "quantity":  1,
  "cartId":    "CART-99213"
}

Response 200:
{
  "reservationId": "RES-77120",
  "expiresAt":     "2026-08-03T09:24:02Z"
}

Response 409:
{
  "error":             "INSUFFICIENT_STOCK",
  "availableQuantity": 0
}

Notice the response includes freshnessState and bufferApplied — exposing these internally lets other teams (like the frontend team showing “Only 2 left!” messaging) make informed decisions rather than treating stock as a single, opaque number.

11.3 Choosing the Network Protocol for Supplier Connections

Most suppliers expose plain REST over HTTPS, since it is the easiest for a third-party company to implement and document. For very high-frequency, high-volume internal communication (for example, between the Feed Ingestion Service and the Inventory Service, both owned by your own platform), gRPC is often preferred over REST because it uses a compact binary format and supports strongly-typed contracts, reducing both payload size and the chance of a field being misread. For supplier-to-platform webhooks specifically, every incoming request should carry a cryptographic signature (commonly HMAC-SHA256) so the receiving service can verify the payload genuinely came from the claimed supplier and was not tampered with in transit, a topic covered further in Chapter 15.

12

Design Patterns and Anti-Patterns

12.1 Useful Patterns

pattern

Circuit Breaker

Stops repeated calls to a failing supplier and enables safe fallback.

pattern

Bulkhead

Isolates resources per supplier (separate thread pools or connection limits) so one bad supplier cannot exhaust resources needed by others.

pattern

Saga (Compensating Transaction)

If an order must later be cancelled due to reconciliation, a saga coordinates the refund, notification, and stock correction as a series of reversible steps.

pattern

Event Sourcing

Storing every stock change as an event (rather than only the final number) makes it possible to audit exactly what happened during an incident.

pattern

Cache-Aside

Keeps reads fast without making the cache the source of truth.

12.1.1 A Closer Look at the Saga Pattern for Compensations

When an oversell is detected after the fact, simply deleting the order from the database is not safe — money may have already been captured, a confirmation email may have already been sent, and a warehouse may have already begun picking the item. A saga breaks the cancellation into a sequence of explicit, independently reversible steps: reverse the payment capture, notify the customer with a clear explanation and an apology (often paired with a discount code), release any held stock back to the pool, and update the order’s status to “cancelled: inventory unavailable” for reporting and audit purposes. If any individual step in this sequence fails (for example, the payment reversal API is temporarily down), the saga retries that step independently rather than leaving the whole cancellation half-finished, which is far safer than treating the cancellation as a single all-or-nothing database transaction across systems that do not share a single transactional boundary.

12.2 Anti-Patterns to Avoid

avoid

Synchronous Supplier Calls on Checkout

Makes your checkout only as reliable as your least reliable supplier.

avoid

Trusting a Feed Blindly

Applying supplier-reported numbers directly without validation or bounds-checking can let a single bad payload (for example, a supplier accidentally sending “999999” units) instantly break your buffer logic.

avoid

One Giant Polling Job

A single slow supplier delays updates for every other supplier queued behind it — ingestion must be per-supplier, not a shared serial job.

avoid

No Reservation TTL

Carts that never expire slowly lock up all remaining stock even though no purchase will ever complete.

avoid

Silent Failure Handling

Catching an exception from a supplier call and doing nothing, with no alert, no log, and no fallback — this is how outages go unnoticed for hours.

13

Performance and Scalability

The read path (customers viewing products and checking out) must remain fast and available even when the write path (supplier feed ingestion) is under stress. Some concrete techniques:

  • Horizontal scaling of ingestion workers: Partition suppliers across many worker instances using consistent hashing, so adding more suppliers means adding more workers, not slowing down existing ones.
  • Batching writes: Group many small stock updates into a single database transaction where possible, reducing per-update overhead.
  • Backpressure on the queue: If the Inventory Service consumers fall behind, the message queue absorbs the backlog rather than the ingestion service blocking or crashing.
  • Read replicas: For very high read traffic on product pages, a read replica of the inventory database (in addition to the Redis cache) can further reduce load on the primary database used for writes.
Production example — Amazon’s fulfilment and marketplace systems separate “seller updates inventory” traffic from “customer browses and buys” traffic into entirely different pipelines with different scaling profiles, specifically so a flood of seller updates during a big sales event never slows down the buying experience.

13.1 Algorithmic Complexity in the Hot Path

The checkout read path — “how much stock is available right now” — is called far more often than the write path that updates stock, so it deserves the most attention to algorithmic efficiency. A Redis hash-map lookup for a single product’s stock is O(1), meaning the time it takes does not grow as your catalog grows from 10,000 to 10,000,000 products. Contrast this with a naive design that scans a list of all products looking for a matching ID, which would be O(n) and would visibly slow down as the catalog grows — exactly the kind of design mistake that looks fine in a small demo and quietly breaks in production at scale.

13.2 Capacity Planning Example

Suppose your platform expects 5,000 product page views per second at peak, and each view requires one stock lookup. A single Redis node can typically handle well over 100,000 simple reads per second, so one well-provisioned cache node comfortably absorbs this load with significant headroom, while the PostgreSQL database behind it is shielded from ever seeing that traffic directly, since cache hits never reach the database at all. This is a concrete illustration of why the cache-aside pattern in Chapter 10.2 exists — it is not just a nice-to-have, it is the difference between the database needing to handle 5,000 queries per second versus a small fraction of that for cache misses and writes only.

14

High Availability and Reliability

  • Multi-AZ deployment: Run the Inventory Service, database, and cache across multiple availability zones so a single data center failure does not take down the whole pipeline.
  • Database replication: A primary-replica PostgreSQL setup with automatic failover ensures writes can continue (via a new primary) if the current primary fails.
  • Queue durability: Kafka’s replicated partitions mean an ingestion event is not lost even if one broker goes down before the Inventory Service consumes it.
  • Graceful degradation: If Redis is unavailable, the system should fall back to reading directly from PostgreSQL (slower, but still correct) rather than failing entirely.
  • Per-supplier isolation: One supplier’s total outage should never be able to take down the ingestion pipeline for any other supplier — this is the bulkhead pattern applied at the infrastructure level.

14.1 Disaster Recovery and Backups

Beyond day-to-day availability, the system needs a plan for catastrophic scenarios — an entire region going offline, or accidental data corruption. Automated daily snapshots of the inventory database, combined with point-in-time recovery (the ability to restore the database to its exact state at, say, 3:42 PM yesterday, replaying transaction logs up to that moment), let the team recover from both infrastructure failures and human error, such as a bad deployment that corrupts stock data. A documented recovery time objective (how long recovery should take) and recovery point objective (how much data loss, in time, is acceptable) should be agreed with the business ahead of time, not decided during an actual incident.

14.2 Failure Recovery for the Message Queue

If the Inventory Service consumer crashes mid-processing, Kafka’s consumer offset mechanism ensures that unprocessed messages are not lost — the offset (a marker of “how far through the queue this consumer has read”) is only committed after a message is successfully processed, so a crashed consumer resumes exactly where it left off when it restarts, rather than skipping messages or needing manual intervention.

15

Security

  • Authenticated supplier connections: Every supplier integration uses API keys or OAuth tokens, rotated regularly, never hardcoded in source code.
  • Payload validation: Never trust incoming feed data blindly — validate types, ranges, and required fields before it touches the database, protecting against both malicious and accidental bad data.
  • Rate limiting inbound webhooks: Prevents a misbehaving or compromised supplier system from flooding your ingestion endpoint.
  • Least privilege for service accounts: The Feed Ingestion Service should only have write access to the inventory tables it needs, not broad database access.
  • Audit logging: Every stock change should be traceable to the exact feed event or reconciliation job that caused it, which is essential both for security investigations and for resolving customer disputes.

15.1 Verifying Webhook Authenticity

Because webhooks are inbound calls initiated by the supplier’s system, your API endpoint must verify that a request genuinely came from that supplier and was not forged or replayed by an attacker. The standard approach is for the supplier to sign each payload with a shared secret key using HMAC-SHA256, sending the resulting signature in a request header. Your service recomputes the signature independently and rejects the request if the two do not match exactly.

WebhookVerifier.java — constant-time HMAC-SHA256 signature check.
public boolean isValidSignature(String payload, String receivedSignature, String sharedSecret) {
    Mac hmac = Mac.getInstance("HmacSHA256");
    hmac.init(new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] computed = hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
    String computedHex = HexFormat.of().formatHex(computed);
    return MessageDigest.isEqual(
        computedHex.getBytes(StandardCharsets.UTF_8),
        receivedSignature.getBytes(StandardCharsets.UTF_8)
    );
}

Using MessageDigest.isEqual rather than a plain string comparison matters here — it performs a constant-time comparison, which prevents an attacker from guessing the correct signature one character at a time by measuring how long a mismatched comparison takes, a technique known as a timing attack.

16

Monitoring, Logging and Metrics

16.1 Key Metrics to Track Per Supplier

metric

Feed Freshness

Seconds since last successful update; directly drives buffer sizing and stale-state transitions.

metric

Circuit Breaker State Changes

Signals when a supplier integration needs human attention.

metric

Feed Error Rate

Early warning of a degrading supplier connection before it fully fails.

metric

Reconciliation Mismatch Count

Measures how often the real-time pipeline and reality diverge.

metric

Oversell Incident Count

The ultimate business-facing metric this whole system exists to keep near zero.

16.2 Alerting Thresholds

Alerts should escalate in severity: a warning when a feed is aging past its normal window, a high-priority page when a circuit breaker opens for a high-volume supplier, and an immediate page if any confirmed overselling is detected by reconciliation, since that requires fast customer-facing remediation (refund, apology, or expedited restock).

i
What an Interviewer May Ask

“How would you detect an oversell has already happened, after the fact?” Compare total reserved-and-confirmed quantity for a product against the last verified supplier quantity for the same window. If confirmed orders exceed what was actually available at any point in time, that is a detected oversell, and the reconciliation job should flag it automatically, log the affected order IDs, and trigger the compensating workflow (cancellation, refund, or expedited sourcing) rather than waiting for a customer complaint.

17

Deployment and Cloud Strategy

This system fits naturally into a containerised, cloud-native deployment:

  • Kubernetes to run the Feed Ingestion, Inventory, Order, and Reconciliation services as independently scalable deployments, each with its own resource limits and horizontal pod autoscaling rules.
  • Managed Kafka (such as Amazon MSK or Confluent Cloud) to avoid operating the message broker cluster by hand.
  • Managed PostgreSQL (such as Amazon RDS or Cloud SQL) with automated backups, point-in-time recovery, and multi-AZ failover.
  • Managed Redis (such as Amazon ElastiCache) for the caching layer.
  • Blue-green or canary deployments for the Inventory Service specifically, since bugs here have direct revenue impact — a canary release lets a new version handle a small percentage of traffic before a full rollout.

17.1 Infrastructure as Code

All of the infrastructure described in this tutorial — the Kubernetes deployments, the Kafka topics, the database instances, the cache clusters — should be defined in code (using a tool such as Terraform) rather than clicked together manually in a cloud console. This makes environments reproducible, lets changes go through code review like any other change, and makes it possible to quickly stand up an identical staging environment for the kind of chaos-engineering failure testing recommended in Chapter 19.1.

17.2 Cost Optimisation

Ingestion workers for low-traffic suppliers do not need to run at the same scale as those handling high-volume suppliers. Autoscaling ingestion worker pools based on actual queue depth and per-supplier call volume, rather than running a fixed large fleet at all times, keeps compute costs proportional to actual load. Similarly, the reconciliation job should prioritise the smaller set of high-risk products identified in Chapter 21 (the FAQ) rather than re-verifying the entire catalog on every run, since supplier API calls are often rate-limited or metered, and unnecessary calls both cost money and consume a shared rate-limit budget that healthy, time-critical checks need.

18

Advantages, Disadvantages and Trade-offs

No architecture is free of trade-offs, and it is worth being explicit about what this design gives up in exchange for what it protects. Every choice below intentionally accepts a small, bounded, well-understood risk in exchange for a much larger benefit in speed, resilience, or simplicity. Being able to explain these trade-offs clearly — not just list the components — is usually what separates a strong system design answer from a merely correct one.

DecisionAdvantageTrade-off
Async pipeline with cache instead of live supplier callsFast, resilient checkout experienceSmall window of eventual consistency between supplier reality and cached view
Safety stock bufferGreatly reduces oversell riskSlightly under-sells available stock, a small amount of lost sales opportunity
Circuit breaker with fallbackProtects system stability during supplier outagesAdds complexity; requires careful threshold tuning to avoid false positives
Reservation with TTLPrevents phantom stock hoarding by abandoned cartsCustomers who take too long may lose their reserved item
Separate reconciliation jobCatches drift the real-time path missesExtra infrastructure and API calls to suppliers just for verification

Overall Advantages

  • Checkout stays fast and available even during a full supplier outage.
  • One misbehaving supplier cannot cascade into damaging every other supplier’s customers.
  • The safety buffer converts a potentially catastrophic oversell into a small, bounded loss of sold-through opportunity.
  • Every stock change is auditable end to end, so incident reviews have real evidence rather than guesswork.
  • The system scales horizontally at every layer: workers, queues, cache, and database shards.

Costs to Accept

  • Additional infrastructure to operate (queue, cache, reconciliation job) compared with a naive design.
  • Some stock is deliberately hidden from customers by the buffer, which is a real if usually small opportunity cost.
  • Circuit breaker and reservation logic must be tuned per supplier — not a single one-size-fits-all setting.
  • Eventual consistency between the cache and database means engineers must reason about staleness rather than assuming perfect freshness.
  • Debugging a specific stock decision requires understanding the full pipeline, not just one database row.
19

Best Practices and Common Mistakes

19.1 Best Practices

  • Size the safety buffer based on real, per-supplier reliability data, not a single fixed number for every supplier.
  • Make every supplier integration independently circuit-broken so one bad partner cannot degrade the whole platform.
  • Treat “no update received” the same seriousness as “update received with an error” — silence is itself a failure signal.
  • Always log the exact source and timestamp behind every stock number, for support and audit purposes.
  • Test failure scenarios deliberately (chaos engineering) — simulate a supplier going fully offline in staging and confirm the buffer and circuit breaker behave as designed before it happens for real.

19.2 Common Mistakes

  • Treating the supplier feed as always correct and never validating incoming numbers.
  • Applying the fix only at the display layer (hiding stock on the webpage) while still letting the backend accept orders beyond real availability.
  • Forgetting to expire stock reservations, silently locking up inventory.
  • Not distinguishing between “supplier says zero stock” (a real answer) and “supplier didn’t respond” (an unknown, which should never be treated the same as zero or as unlimited).
  • Building monitoring only for uptime of the pipeline itself, and forgetting to monitor the actual business outcome — oversell incidents.
  • Hardcoding a single global timeout and retry policy for every supplier, instead of tuning per-supplier settings based on their actual observed latency and reliability characteristics.
  • Skipping load testing of the ingestion pipeline itself — a supplier recovering from an outage often sends a large backlog of updates all at once, and if the pipeline was never tested against that kind of burst, it can fall over at exactly the moment it is most needed.

19.3 A Short Checklist Before Going Live

Before launching a supplier integration into production, it helps to walk through a short, concrete checklist rather than relying on memory:

Pre-launch check
1Does every outbound call to this supplier have a timeout and a circuit breaker configured?
2Is there a documented, tested fallback behaviour for when this supplier’s feed goes stale or fails?
3Is the safety buffer policy for this supplier’s products configured, and does it scale up with staleness?
4Are reservation TTLs configured for every checkout flow that touches this supplier’s products?
5Is this supplier included in the reconciliation job’s schedule, with an appropriate frequency for its products’ sales velocity?
6Are freshness, error-rate, and circuit-breaker-state alerts wired up and pointed at an actual on-call rotation?
7Has the team run a simulated outage of this supplier in staging and confirmed the system behaves as designed, without any code that only exists in someone’s head?
20

Real-World Industry Examples

The patterns above aren’t theoretical: they show up in the real marketplace and drop-shipping platforms that keep millions of customers from ever seeing an oversell.

case A

Amazon Marketplace

Requires third-party sellers to maintain accurate inventory feeds and has automated systems that detect feed staleness, applying seller performance penalties and temporary listing suppression for repeated inventory inaccuracy, precisely because oversold orders damage customer trust in the whole platform, not just one seller.

case B

Walmart Marketplace

Publishes explicit inventory feed frequency requirements to its suppliers and uses buffer logic on fast-moving items, showing more conservative stock counts for products with high order velocity relative to feed freshness.

case C

Flipkart

Operating heavily on a marketplace and multi-seller model in India, uses reservation-based checkout flows where stock is held for a limited window during payment, releasing it back to the pool automatically if payment does not complete — directly mirroring the reservation-with-TTL pattern covered in Chapter 9.

case D

Shopify

Powering many independent stores that connect to drop-shipping suppliers, provides built-in inventory tracking with configurable “oversell protection,” letting merchants choose whether to allow continued selling when a connected feed reports zero or stale stock.

case E

Uber Eats & Food Delivery

Face a compressed version of this same problem: a restaurant’s kitchen “inventory” (whether a dish is available) can change in seconds, not hours. These platforms rely heavily on push-based updates and very short freshness windows, since even a five-minute-old feed is considered unacceptably stale for a menu item, illustrating how the same architecture can be tuned with very different freshness thresholds depending on the domain.

21

Frequently Asked Questions

The most common questions that arise when engineers first approach this design, answered directly and without hedging.

Q1Should we ever allow overselling on purpose?

Some businesses deliberately allow a small, controlled amount of overselling for extremely fast-moving or backorderable items, treating it as a calculated business risk with clear customer communication (“ships in 2–3 weeks”), rather than an accidental failure. That is a business decision layered on top of this architecture, not a replacement for it.

Q2How fresh does a feed really need to be?

It depends entirely on the product’s sales velocity. A slow-moving item that sells once a week can tolerate an hour of staleness with almost no risk. A flash-sale item selling every few seconds needs near real-time freshness, or it needs a much larger safety buffer to compensate.

Q3What happens to an order placed during a stale window that turns out to be invalid?

The order enters a provisional state (see Chapter 9.1). If reconciliation confirms it cannot be fulfilled, an automated compensating workflow (Chapter 12.1, Saga pattern) cancels the order, issues a refund, and notifies the customer, ideally offering an incentive like a discount on a future order to preserve goodwill.

Q4Can this design scale to thousands of suppliers?

Yes — because each supplier’s ingestion is isolated (bulkheaded) and horizontally partitioned across workers, adding suppliers means adding processing capacity, not redesigning the system. The message queue and cache layers are also designed to scale horizontally.

Q5How do you choose the right circuit breaker thresholds?

Start conservative (for example, open after 50% of the last 20 calls fail) and tune using real historical data per supplier once you have it. A supplier with naturally spiky but recoverable latency needs a more forgiving threshold than a supplier that, historically, either works perfectly or fails completely. Treating every supplier identically is a common early mistake; mature systems maintain per-supplier configuration profiles built from observed behaviour.

Q6What if the supplier’s feed reports incorrect but plausible-looking numbers, not an outright failure?

This is the hardest case, because the circuit breaker and freshness checks will not catch it — the feed appears healthy and on time, but the number itself is simply wrong. This is precisely why the reconciliation job in Chapter 9 exists as an independent check: it does not just verify that a feed is arriving, it periodically re-verifies that the arriving numbers are actually correct against a fresh, direct read, catching silent data-quality problems that freshness-based checks alone cannot see.

Q7Does every product need the same level of protection?

No. Applying the heaviest safety buffers, most frequent reconciliation, and tightest circuit breaker thresholds to every single product in a catalog of millions would be wasteful. Mature systems tier products by risk — based on sales velocity, price, and historical feed reliability — and concentrate the most expensive protections (like frequent reconciliation calls) on the smaller set of high-risk, fast-moving products where an oversell is most likely and most damaging.

22

Summary and Key Takeaways

Handling supplier inventory feed delays and failures sits at the intersection of e-commerce and distributed systems — let’s condense everything into the ideas worth carrying forward.

Key Takeaways

  • Overselling happens when a stale or failed supplier feed lets your platform accept more orders than actually exist — the fix is architectural, not just a bug patch.
  • Decouple your checkout path from live supplier calls using an asynchronous ingestion pipeline, a message queue, and a local cache.
  • Use a circuit breaker per supplier to detect failures quickly and switch to safe fallback behaviour instead of blindly retrying or trusting stale data.
  • Apply a dynamic safety stock buffer that grows as feed data ages, and treat a fully stale feed as “unknown,” not as “unlimited” or automatically “zero.”
  • Protect checkout with reservation and optimistic locking so concurrent buyers can never both “win” the last unit.
  • Run an independent reconciliation job as a safety net that catches anything the real-time pipeline missed.
  • Monitor feed freshness, circuit breaker states, and oversell incidents as first-class business metrics, not just technical uptime numbers.
  • Real companies like Amazon, Walmart, Flipkart, and Shopify all apply variations of these same patterns because the underlying problem — trusting a third party’s data across an unreliable network — is universal in any marketplace or drop-shipping business.
💡
Final Thought

If you take away one idea from this entire tutorial, let it be this: overselling is not really an “inventory bug,” it is a distributed systems problem wearing an inventory costume. The moment stock information has to travel across a network boundary you do not control, you are dealing with the same fundamental challenges as any other distributed system — unreliable communication, partial failure, out-of-order data, and the need to make a confident decision under uncertainty. Every technique in this tutorial, from circuit breakers to optimistic locking to reconciliation jobs, exists to convert that underlying uncertainty into a small, bounded, well-monitored risk instead of an unpredictable one. That reframing is exactly what turns a fragile integration into a resilient one, and it is the mental model worth carrying into any other system you design where your platform depends on data owned by someone else.