Machine Coding Problem

Coupon Management System

maco60macoAllcommercerule-enginestacking-logic
Commonly Asked By:AmazonWalmartShopify

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic Coupon Rules Engine: Evaluate discount coupons (Percentage, Flat, BOGO) using composite boolean checks (AND predicates).
  • Contextual Applicability Predicates: Validate conditions such as minimum order value limits, eligible categories, and user segmentation profiles using the Specification Pattern.
  • Stacking Policy Managers: Restrict cart items to exclusive coupons or apply stacking guidelines (Additive vs Best Only).
  • Atomic Usage Decrements: Protect coupon pools with atomic, thread-safe counter decrements during concurrent checkout processes.

Explicit Boundaries (Out-of-Scope)

  • No Third-Party Banking Ledger Settlements: Excludes live payment processor balance checks or external credit card settlements.
  • No Automatic AI Fraud Detection Models: Bypasses complex pattern matching (ML models) to identify checkout bots or duplicate account usage.

Class Diagram & Entity Relationships

Structural layout showing relationships between cart contexts, rule engines, and usage caps:

Loading...
  • Predicate Rule Chains: Coupon wraps configuration rules alongside thread-safe atomic limit counters.
  • Cart Context Values: OrderContext represents purchase totals, category flags, and user segments.

Design Patterns & SOLID Principles

  • Specification Pattern (Applicability Predicates): Encapsulating conditional business rules (e.g., minimum cart value, target category) within clean logic checks keeps rule sets isolated and easy to unit test, satisfying SRP.
  • Strategy Pattern (Stacking Policies): Decoupling code stacking calculations (Exclusive vs Additive) into distinct strategies allows discount calculations to be adjusted dynamically at checkout, satisfying OCP.

Core Execution Workflows

Coupon Application Lifecycle

  1. Renter enters promo code: applyCoupon(orderId, couponCode, context).
  2. Fetch Coupon details and evaluate rules:
    1. If minimum order limit is not met, return WA (invalid rule parameters).
    2. Verify expiration dates and user segment profile matches.
  3. Evaluate stacking logic against other applied coupons.
  4. Execute atomic checkout decrement:
    1. If remaining uses > 0, decrement counter atomicly (CAS).
    2. Otherwise, fail the application and inform the buyer.
  5. If the order is subsequently cancelled, trigger rollback logic: release() (increment usage counter back).

Concurrency & Thread Safety Strategy

When thousands of users apply the same high-value coupon simultaneously during a flash sale:

  • Atomic CAS Decrements: Protect coupon usage counters using non-blocking atomic variables (e.g., Java's AtomicInteger) to prevent overselling.
  • Idempotent Operations: Tie coupon redemptions to unique order IDs to prevent duplicate discount application bugs.

Complete Clean Code Blueprint

Clean reference designs demonstrating atomic discount checkouts in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

enum DiscountType {
    FLAT,
    PERCENTAGE,
    BOGO
}

// Context containing order details
class OrderContext {
    private final String orderId;
    private final String userId;
    private final double totalAmount;
    private final String category;
    private final String userSegment;

    public OrderContext(String orderId, String userId, double totalAmount, String category, String userSegment) {
        this.orderId = orderId;
        this.userId = userId;
        this.totalAmount = totalAmount;
        this.category = category;
        this.userSegment = userSegment;
    }

    public String getOrderId() { return orderId; }
    public String getUserId() { return userId; }
    public double getTotalAmount() { return totalAmount; }
    public String getCategory() { return category; }
    public String getUserSegment() { return userSegment; }
}

// Specification Pattern for Applicability Rules
interface CouponSpecification {
    boolean isSatisfiedBy(OrderContext context);
}

class MinAmountSpecification implements CouponSpecification {
    private final double minAmount;
    public MinAmountSpecification(double minAmount) { this.minAmount = minAmount; }
    @Override
    public boolean isSatisfiedBy(OrderContext context) {
        return context.getTotalAmount() >= minAmount;
    }
}

class CategorySpecification implements CouponSpecification {
    private final String allowedCategory;
    public CategorySpecification(String allowedCategory) { this.allowedCategory = allowedCategory; }
    @Override
    public boolean isSatisfiedBy(OrderContext context) {
        return context.getCategory().equalsIgnoreCase(allowedCategory);
    }
}

class UserSegmentSpecification implements CouponSpecification {
    private final String requiredSegment;
    public UserSegmentSpecification(String requiredSegment) { this.requiredSegment = requiredSegment; }
    @Override
    public boolean isSatisfiedBy(OrderContext context) {
        return context.getUserSegment().equalsIgnoreCase(requiredSegment);
    }
}

class AndSpecification implements CouponSpecification {
    private final List<CouponSpecification> specs = new ArrayList<>();
    public AndSpecification(CouponSpecification... specifications) {
        specs.addAll(Arrays.asList(specifications));
    }
    @Override
    public boolean isSatisfiedBy(OrderContext context) {
        for (CouponSpecification spec : specs) {
            if (!spec.isSatisfiedBy(context)) return false;
        }
        return true;
    }
}

// Coupon Entity
class Coupon {
    private final String code;
    private final DiscountType discountType;
    private final double discountValue;
    private final CouponSpecification specification;
    private final AtomicInteger remainingUsage;
    private final int maxUsage;

    public Coupon(String code, DiscountType discountType, double discountValue, CouponSpecification specification, int maxUsage) {
        this.code = code;
        this.discountType = discountType;
        this.discountValue = discountValue;
        this.specification = specification;
        this.maxUsage = maxUsage;
        this.remainingUsage = new AtomicInteger(maxUsage);
    }

    public String getCode() { return code; }
    public DiscountType getDiscountType() { return discountType; }
    public double getDiscountValue() { return discountValue; }
    public CouponSpecification getSpecification() { return specification; }
    public int getRemainingUsage() { return remainingUsage.get(); }

    public boolean isApplicable(OrderContext context) {
        return specification.isSatisfiedBy(context);
    }

    public boolean tryRedeem() {
        while (true) {
            int current = remainingUsage.get();
            if (current <= 0) return false;
            if (remainingUsage.compareAndSet(current, current - 1)) {
                return true;
            }
        }
    }

    public void release() {
        while (true) {
            int current = remainingUsage.get();
            if (current >= maxUsage) return;
            if (remainingUsage.compareAndSet(current, current + 1)) {
                return;
            }
        }
    }

    public double calculateDiscount(double amount) {
        switch (discountType) {
            case FLAT:
                return Math.min(amount, discountValue);
            case PERCENTAGE:
                return amount * (discountValue / 100.0);
            case BOGO:
                // Buy One Get One style - represented as 50% discount for simplicity or custom calculation
                return amount * 0.5;
            default:
                return 0.0;
        }
    }
}

// Strategy Pattern for Coupon Stacking Rules
interface StackingStrategy {
    double calculateFinalDiscount(List<Coupon> coupons, OrderContext context);
}

class ExclusiveStackingStrategy implements StackingStrategy {
    @Override
    public double calculateFinalDiscount(List<Coupon> coupons, OrderContext context) {
        if (coupons.isEmpty()) return 0.0;
        if (coupons.size() > 1) {
            throw new IllegalArgumentException("Exclusive policy violated: multiple coupons not allowed.");
        }
        Coupon coupon = coupons.get(0);
        return coupon.isApplicable(context) ? coupon.calculateDiscount(context.getTotalAmount()) : 0.0;
    }
}

class AdditiveStackingStrategy implements StackingStrategy {
    @Override
    public double calculateFinalDiscount(List<Coupon> coupons, OrderContext context) {
        double currentTotal = context.getTotalAmount();
        double totalDiscount = 0.0;
        for (Coupon coupon : coupons) {
            if (coupon.isApplicable(context)) {
                double discount = coupon.calculateDiscount(currentTotal);
                totalDiscount += discount;
                currentTotal = Math.max(0, currentTotal - discount);
            }
        }
        return totalDiscount;
    }
}

class BestOnlyStackingStrategy implements StackingStrategy {
    @Override
    public double calculateFinalDiscount(List<Coupon> coupons, OrderContext context) {
        double bestDiscount = 0.0;
        for (Coupon coupon : coupons) {
            if (coupon.isApplicable(context)) {
                double discount = coupon.calculateDiscount(context.getTotalAmount());
                bestDiscount = Math.max(bestDiscount, discount);
            }
        }
        return bestDiscount;
    }
}

// Central Coupon Manager
class CouponManager {
    private final Map<String, Coupon> couponRegistry = new ConcurrentHashMap<>();

    public void addCoupon(Coupon coupon) {
        couponRegistry.put(coupon.getCode().toUpperCase(), coupon);
    }

    public Coupon getCoupon(String code) {
        return couponRegistry.get(code.toUpperCase());
    }

    public boolean redeemCoupons(List<String> codes, OrderContext context, StackingStrategy strategy) {
        List<Coupon> couponsToApply = new ArrayList<>();
        for (String code : codes) {
            Coupon coupon = getCoupon(code);
            if (coupon == null || !coupon.isApplicable(context)) {
                System.out.println("Coupon " + code + " is either invalid or not applicable for Order " + context.getOrderId());
                return false;
            }
            couponsToApply.add(coupon);
        }

        // Validate final discount computation succeeds under current strategy
        try {
            double finalDiscount = strategy.calculateFinalDiscount(couponsToApply, context);
            System.out.println("Computed final discount: $" + String.format("%.2f", finalDiscount) + " for Order " + context.getOrderId());
        } catch (IllegalArgumentException e) {
            System.out.println("Checkout failed coupon stacking check: " + e.getMessage());
            return false;
        }

        // Try atomic redemption
        List<Coupon> successfullyRedeemed = new ArrayList<>();
        boolean success = true;

        for (Coupon coupon : couponsToApply) {
            if (coupon.tryRedeem()) {
                successfullyRedeemed.add(coupon);
            } else {
                success = false;
                break;
            }
        }

        // Rollback if any redemption failed
        if (!success) {
            System.out.println("Checkout failed: One or more coupons exceeded usage limit. Rolling back.");
            for (Coupon coupon : successfullyRedeemed) {
                coupon.release();
            }
            return false;
        }

        System.out.println("Order " + context.getOrderId() + " successfully redeemed coupons: " + codes);
        return true;
    }

    public void releaseCoupons(List<String> codes) {
        for (String code : codes) {
            Coupon coupon = getCoupon(code);
            if (coupon != null) {
                coupon.release();
                System.out.println("Released usage lock for coupon: " + code);
            }
        }
    }
}

// Execution Test Driver
public class Main {
    public static void main(String[] args) throws InterruptedException {
        CouponManager manager = new CouponManager();

        // 1. Register diverse coupons with complex Specifications
        Coupon specCoupon1 = new Coupon("SAVE20", DiscountType.PERCENTAGE, 20.0,
            new AndSpecification(
                new MinAmountSpecification(100.0),
                new UserSegmentSpecification("VIP")
            ), 5); // Max usage 5

        Coupon specCoupon2 = new Coupon("FLAT15", DiscountType.FLAT, 15.0,
            new CategorySpecification("Electronics"), 10);

        Coupon specCoupon3 = new Coupon("EXCLUSIVE50", DiscountType.PERCENTAGE, 50.0,
            new MinAmountSpecification(200.0), 1); // Flash sale single coupon limit

        manager.addCoupon(specCoupon1);
        manager.addCoupon(specCoupon2);
        manager.addCoupon(specCoupon3);

        // 2. Validate Order Applicability
        OrderContext context1 = new OrderContext("O-100", "U-VIP", 150.0, "Electronics", "VIP");
        System.out.println("Is SAVE20 applicable for VIP purchasing Electronics ($150)? " + specCoupon1.isApplicable(context1));
        System.out.println("Is FLAT15 applicable for VIP purchasing Electronics ($150)? " + specCoupon2.isApplicable(context1));

        // 3. Stacking Policy Application
        List<Coupon> currentApplied = Arrays.asList(specCoupon1, specCoupon2);
        StackingStrategy additive = new AdditiveStackingStrategy();
        double discountVal = additive.calculateFinalDiscount(currentApplied, context1);
        System.out.println("Combined Additive discount (SAVE20 + FLAT15): $" + discountVal);

        // 4. Concurrent Redemption Test simulation
        ExecutorService executor = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(4);

        System.out.println("\n--- Simulating 4 concurrent checkouts for a single limit exclusive coupon (EXCLUSIVE50) ---");
        for (int i = 0; i < 4; i++) {
            final int id = i;
            executor.submit(() -> {
                OrderContext order = new OrderContext("O-CONC-" + id, "U-" + id, 300.0, "Electronics", "Regular");
                boolean status = manager.redeemCoupons(Collections.singletonList("EXCLUSIVE50"), order, new ExclusiveStackingStrategy());
                System.out.println("Checkout " + id + " success: " + status);
                latch.countDown();
            });
        }

        latch.await();
        executor.shutdown();

        System.out.println("Final EXCLUSIVE50 remaining usage count: " + specCoupon3.getRemainingUsage());
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...