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
- Normalize code to uppercase; look up PromoCode by code.
- Check
is_active, promotion date range, andends_at. - Evaluate EligibilityRules against the current cart and user context.
- Check global
uses_count < max_uses(skip if max_uses is NULL). - Check per-user redemption count in Redemption table.
- Check stackability: if cart already has a non-stackable promo applied, reject.
- 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:
- Begin transaction.
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.- If 0 rows affected, the code is exhausted; abort order or fall back to no discount.
- Insert Redemption row with
order_idas UNIQUE key for idempotency. - 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 againstper_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
PromoRedeemedevent 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