Low Level Design: Promotional Code Engine

What Is a Promotional Code Engine?

A promotional code engine generates discount codes, enforces usage rules, validates eligibility at checkout, tracks redemptions, and feeds analytics. It is a rich low level design problem because it combines code generation, rule evaluation, concurrency control, and reporting.

Functional Requirements

  • Create a promotion with a discount type (percent off, fixed amount, free shipping, BOGO).
  • Generate one or more promo codes associated with that promotion.
  • Define usage limits: global max uses, per-user max uses, single-use codes.
  • Define eligibility conditions: minimum order value, specific SKUs, user segments, date range.
  • Define stackability: whether a code can combine with other active promotions.
  • Validate and redeem a code at checkout.
  • Report redemption counts, revenue impact, and conversion rates per promotion.

Non-Functional Requirements

  • Validation must be idempotent and complete in <100 ms.
  • No overselling: global usage limit must never be exceeded under concurrent load.
  • Codes are case-insensitive and resistant to brute-force guessing.

Core Entities

Promotion
  id              UUID PK
  name            VARCHAR(128)
  discount_type   ENUM('PERCENT','FIXED','FREE_SHIPPING','BOGO')
  discount_value  DECIMAL(10,2)
  stackable       BOOLEAN
  starts_at       TIMESTAMP
  ends_at         TIMESTAMP NULL
  created_at      TIMESTAMP

PromoCode
  id              UUID PK
  promotion_id    UUID FK -> Promotion
  code            VARCHAR(32) UNIQUE  -- stored uppercase
  max_uses        INT NULL            -- NULL = unlimited
  uses_count      INT DEFAULT 0
  per_user_limit  INT NULL
  is_active       BOOLEAN DEFAULT TRUE
  created_at      TIMESTAMP

EligibilityRule
  id              UUID PK
  promotion_id    UUID FK -> Promotion
  rule_type       ENUM('MIN_ORDER','SKU_LIST','USER_SEGMENT','FIRST_ORDER')
  rule_value      JSONB               -- e.g. {"min": 50.00} or {"skus": ["A1","B2"]}

Redemption
  id              UUID PK
  promo_code_id   UUID FK -> PromoCode
  user_id         UUID
  order_id        UUID UNIQUE
  discount_applied DECIMAL(10,2)
  redeemed_at     TIMESTAMP

Code Generation

Two patterns: (1) Vanity codes — human-readable strings like SUMMER20, created by marketing and inserted directly. (2) Bulk unique codes — system-generated for single-use campaigns (e.g., referral links). For bulk generation, use a cryptographically random base-32 string of at least 10 characters to make brute-force infeasible (32^10 > 10^15 combinations). Pre-generate a pool and store inactive; activate on distribution.

Validation Flow at Checkout

  1. Normalize code to uppercase; look up PromoCode by code.
  2. Check is_active, promotion date range, and ends_at.
  3. Evaluate EligibilityRules against the current cart and user context.
  4. Check global uses_count < max_uses (skip if max_uses is NULL).
  5. Check per-user redemption count in Redemption table.
  6. Check stackability: if cart already has a non-stackable promo applied, reject.
  7. Return calculated discount; do NOT increment uses_count yet.

Redemption and Concurrency

Validation (step above) is read-only and cheap. Actual redemption happens when the order is confirmed:

  1. Begin transaction.
  2. UPDATE promo_codes SET uses_count = uses_count + 1 WHERE id = ? AND (max_uses IS NULL OR uses_count < max_uses) — atomic increment with guard.
  3. If 0 rows affected, the code is exhausted; abort order or fall back to no discount.
  4. Insert Redemption row with order_id as UNIQUE key for idempotency.
  5. Commit.

The atomic UPDATE guard prevents overselling without a separate SELECT FOR UPDATE, reducing lock contention.

Usage Limits and Per-User Enforcement

  • Global limit: enforced by the atomic increment above.
  • Per-user limit: query COUNT(*) FROM redemptions WHERE promo_code_id = ? AND user_id = ?; compare against per_user_limit. Do this inside the transaction for correctness.
  • Single-use codes: max_uses = 1; the same mechanism applies.

Stackability Rules

Each Promotion carries a stackable flag. At validation time, inspect all promo codes already applied to the cart. If any applied promotion has stackable = FALSE, reject any additional code. If the new code has stackable = FALSE, reject if any code is already applied. This gives a simple but effective two-flag policy; extend with a priority or exclusion-group column for more nuanced rules.

Eligibility Conditions

EligibilityRule rows are evaluated as an AND chain (all rules must pass). Rule types:

  • MIN_ORDER: cart subtotal >= rule_value.min.
  • SKU_LIST: cart must contain at least one SKU in rule_value.skus.
  • USER_SEGMENT: user must belong to a named segment (resolved via user-service).
  • FIRST_ORDER: user has zero prior completed orders.

Store rule_value as JSONB for flexibility; add a rule_type enum variant and a corresponding evaluator class for each new condition type without schema changes.

Analytics

  • Redemption table is the source of truth for analytics: count redemptions, sum discount_applied, join to orders for revenue and conversion metrics.
  • For real-time dashboards, publish a PromoRedeemed event to a message bus on each successful redemption; downstream consumers update OLAP aggregates.
  • A/B test promotions by randomly assigning users to a treatment code vs. control; use user_id hash for determinism.

API Design

POST /promotions                          -- create promotion + rules
POST /promotions/{id}/codes               -- generate codes (bulk or vanity)
GET  /promotions/{id}/codes               -- list codes with use counts
POST /promo-codes/validate                -- {code, cart, user_id} -> discount
POST /promo-codes/redeem                  -- {code, order_id, user_id, amount}
GET  /promotions/{id}/analytics           -- redemption stats

Interview Tips

  • Separate validation (read-only, cheap, early in checkout) from redemption (write, at order confirmation).
  • Show the atomic increment UPDATE pattern — it is the idiomatic answer to the overselling concurrency problem.
  • Discuss JSONB for eligibility rules to avoid schema churn as the marketing team adds new conditions.
  • Mention the event-driven analytics path; interviewers appreciate separation of operational and reporting concerns.

Frequently Asked Questions

What is a promotional code engine?

A promotional code engine is a rules evaluation service that determines whether a discount applies to a given cart or order and computes the resulting price reduction. It manages the full lifecycle of promotions: creation (code, discount type, constraints), validation (expiry, eligibility, usage limits), application (price calculation), and accounting (redemption recording). The engine decouples discount logic from the checkout service so that new promotion types can be added without modifying order processing code. Internally it typically represents each promotion as a set of conditions (minimum order value, eligible SKUs, user segments) and an effect (percent off, fixed amount off, free shipping).

How do you prevent race conditions in promo code redemption?

Race conditions occur when a code with a limited redemption count is applied simultaneously by multiple users. The standard solution is an atomic compare-and-increment in the database: a SQL UPDATE with a WHERE clause checking that the current count is below the limit, returning the number of affected rows. Zero affected rows means the limit was hit concurrently and the redemption is rejected. For very high-traffic codes, a Redis counter initialized to the remaining quota and decremented with DECR provides lower-latency enforcement; a background job reconciles Redis with the database. Idempotency keys on the redemption request prevent double-counting if the client retries after a network timeout.

How do you design stackable vs exclusive promo rules?

Each promotion carries a stacking policy attribute: exclusive (no other promotions may apply), stackable (may combine with other stackable promotions), or additive-only (stacks but only with same-type discounts). During cart evaluation the engine sorts applicable promotions by priority, applies the first exclusive promotion found and discards the rest, or accumulates all stackable promotions if no exclusive one is present. The discount application order matters for percent-off codes: they should be applied to the post-previous-discount price or to the original price depending on business rules, and that behavior must be encoded explicitly. A rule DSL (e.g., JSON-schema-defined conditions) lets merchandising teams configure stacking behavior without engineering involvement.

How do you handle promo abuse at scale?

Promo abuse includes code sharing beyond intended audiences, multi-account farming, and bot-driven bulk redemptions. Mitigations layer across the stack. Single-use codes are hashed and stored; each code is bound to a user ID at issuance. Email-domain and payment-fingerprint deduplication catches users creating multiple accounts. Device fingerprinting and behavioral signals (order velocity, address reuse) feed a risk score that gates redemption or triggers step-up verification. Rate limits per IP and per account prevent bulk API probing. For leaked bulk codes, the engine supports emergency deactivation by campaign ID without touching individual code records. Post-campaign analytics compare redemption demographics against the intended audience to tune future eligibility constraints.

See also: Shopify Interview Guide

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

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

Scroll to Top