Low Level Design: Dynamic Pricing Engine

A dynamic pricing engine adjusts prices in real time based on demand signals, inventory, and business rules. The design must ingest signals with low latency, evaluate rules quickly, apply guardrails to prevent harmful price swings, propagate updates reliably, and support controlled experiments on pricing models.

Demand Signal Sources

The engine consumes signals from multiple sources, each with different latency and granularity:

  • Request rate: real-time count of product page views and add-to-cart events per SKU per minute, streamed from the event pipeline (Kafka topic: product.events). High request rate relative to baseline indicates elevated demand.
  • Inventory level: current stock count from the inventory service, updated on each fulfillment or restock event. Low inventory relative to a reorder threshold triggers scarcity multipliers.
  • Competitor price: periodic scrape or feed from a price intelligence vendor. Updated every 15–60 minutes. Used for relative positioning rules (e.g., stay within 5% of the lowest competitor).
  • Time of day / day of week: deterministic signal from the system clock. Used for scheduled pricing windows (e.g., happy hour, weekend rates, peak commute hours for rides).
  • Historical demand curve: pre-computed from ML model outputs, stored per SKU per time bucket. Feeds the base price calculation.

Signals are ingested into a signals aggregator service that maintains a per-SKU in-memory snapshot updated on each event. The snapshot is the input to the pricing pipeline.

Price Rule Evaluation Pipeline

Pricing is computed as a sequential pipeline: base price → multiplier chain → floor/ceiling clamp → final price.

  • Base price: the standard list price for the SKU, read from the catalog service. This is the anchor.
  • Demand multiplier: derived from the normalized demand signal. If request rate is 2x the 7-day average, apply a 1.2x multiplier (configurable curve).
  • Inventory multiplier: if stock clearance_threshold, apply a markdown multiplier (e.g., 0.85x).
  • Competitive positioning rule: if final_price > competitor_min_price * (1 + max_premium_pct), clamp down to competitor_min_price * (1 + max_premium_pct).
  • Time-of-day rule: if current time is in a configured pricing window, apply the window multiplier (may be > 1 or < 1).
  • Stacking: multipliers are applied sequentially. Some rules are additive instead of multiplicative — the rule config specifies operation type.

Rules are stored in a config table with priority ordering. The pipeline evaluates rules in priority order and produces a pre-clamp price.

Surge Multiplier Calculation

For ride-sharing or time-sensitive inventory, surge is calculated as:

surge_multiplier = 1 + (demand_index - 1) * surge_sensitivity
demand_index = smoothed_request_rate / baseline_request_rate
smoothed_request_rate = exponential_moving_average(request_rate, alpha=0.3)

surge_sensitivity is a tunable parameter (0 = no surge, 1 = full proportional surge). The EMA smoothing prevents the multiplier from reacting to momentary spikes from a single viral post or bot traffic. Surge values are computed every 60 seconds per SKU or zone.

Floor and Ceiling Guardrails

Guardrails prevent pricing that is technically correct per the rules but harmful to the business or customers:

  • Price floor: minimum price = max(cost_basis * min_margin_multiplier, regulatory_minimum). Prevents selling below cost or legal minimums.
  • Price ceiling: maximum price = base_price * max_surge_cap (e.g., 3.0x). Hard cap regardless of demand signals. Essential for anti-price-gouging compliance during emergencies.
  • Max delta per interval: price cannot change by more than X% in a single pricing cycle. Prevents violent swings that confuse users or trigger repricing wars with competitors.
  • Regulatory windows: in some jurisdictions, price increases during declared emergencies are blocked entirely. A geo + event type blocklist disables surge for affected SKUs/regions.

Price Update Write Path

When the pipeline computes a new price for a SKU, the write path is:

  • Write to the prices table: sku_id, price, computed_at, rule_snapshot (JSON of which rules fired and at what value — useful for debugging).
  • Invalidate or update the price cache (Redis key: price:{sku_id}). Write-through preferred over cache-aside to keep cache consistent.
  • Publish a price.updated event to the event bus (Kafka). Consumers include: search indexing service (re-index the SKU with new price for faceted price filtering), product display service (update CDN-cached product pages), email/notification service (trigger price drop alerts for wishlisted items).

The write is idempotent: if the new computed price equals the current price, skip the write and cache update to avoid unnecessary churn.

Catalog Price vs. Checkout Price Reconciliation

The price shown on the product page (catalog price) may differ from the price at checkout if the pricing engine updated the price between page view and cart submission. Two strategies:

  • Price lock: on add-to-cart, lock the displayed price in the cart session for a TTL (e.g., 15 minutes). Checkout uses the locked price if within TTL, re-prices if expired. Good for conversion but may leave money on the table during high-demand surges.
  • Re-price on checkout: always re-evaluate at checkout time. Show the user the current price and a change notification if it differs from the browsing price. Used for high-volatility products (flights, event tickets).

A/B Testing of Pricing Models

The pricing engine integrates with the experimentation framework to test different rule configurations or model parameters. A pricing experiment assigns users (or SKUs, or geographic zones) to control and treatment groups. The treatment group receives prices computed by the experimental rule set; the control group receives the baseline rule set.

Experiment assignment is deterministic: hash(user_id + experiment_id) mod 100 < treatment_pct. The experiment config service returns the active rule set for a given user/SKU context. The rule_snapshot stored with each price row records which experiment variant was active, enabling post-hoc analysis of revenue, conversion rate, and margin impact per variant. Experiments are ramped gradually (5% → 20% → 50% → 100%) with automated guardrails that halt rollout if key metrics regress beyond a threshold.

Frequently Asked Questions

What is a dynamic pricing engine in system design?

A dynamic pricing engine adjusts prices in real time based on supply, demand, market conditions, and business rules. It ingests signals (inventory levels, competitor prices, demand forecasts, time-of-day patterns), runs them through a pricing model (rule-based, ML, or hybrid), and emits a price that downstream services display to users. Key design concerns are low-latency reads (<50 ms for checkout), consistency between the displayed price and the charged price, guardrails to prevent runaway prices, and auditability of every price change.

What signals does a dynamic pricing engine use to calculate price?

Common signals include: real-time supply (available drivers, hotel rooms, inventory units), real-time demand (active searches, open sessions, cart additions), historical demand curves for the same time slot, competitor pricing scraped or received via feeds, external events (concerts, weather, holidays), user segment or willingness-to-pay model scores, and elapsed time since last price update. These signals are typically joined in a feature store and fed to a pricing model. High-frequency signals (supply/demand) come from streaming pipelines; lower-frequency signals (competitor prices, forecasts) are refreshed in batch.

How do you prevent price gouging with guardrails in a dynamic pricing system?

Implement a layered guardrail stack: (1) hard absolute floor and ceiling per product category, enforced in the price computation layer before any output is emitted; (2) relative change caps — price cannot increase more than X% in a rolling window — to dampen spike artifacts; (3) a surge ratio cap comparing current price to a baseline (e.g. 7-day median), flagging anything above a threshold for human review or automatic rollback; (4) market-level circuit breakers that freeze prices if anomalous conditions are detected (data pipeline outage, model score outside valid range). All guardrail overrides are logged for compliance and audit.

How does Uber implement surge pricing?

Uber’s surge pricing (now called Upfront Pricing) divides a city into geohash hexagons (using H3) and continuously measures the ratio of open ride requests to available drivers in each cell. When demand exceeds supply beyond a threshold, a surge multiplier is applied to the base fare for that cell. The multiplier is computed by a real-time pipeline ingesting GPS pings and request events, running a supply-demand model, and writing the result to a low-latency cache (Redis) that the API reads on every fare estimate call. Guardrails cap the maximum multiplier; messaging to riders explains the surge to manage expectation and reduce cancellations. Over time Uber shifted from visible multipliers to opaque upfront fares, hiding the multiplier while retaining the underlying demand-based calculation.

See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering

See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top