Machine Coding Problem

Auction System

maco60macoAllcommerceobserverhigh-freq-bidding
Commonly Asked By:eBayAlibabaSotheby's

Requirements & System Scope

Functional Scope (In-Scope)

  • High-Frequency Bidding Engine: Thread-safe core validating, sequencing, and committing thousands of bids per second.
  • Ebay-style Proxy Bidding: Automatic incremental bidding up to a user-configured maximum amount, showing only the minimal required outbid value.
  • Anti-Sniping Logic: Dynamic deadline extension if a valid bid lands within the configured last-minute buffer window (e.g., last 5 minutes).
  • Bid Accepted Observers: Reactive event notification pipelines updating active dashboards, alert subsystems, and payment gateways.

Explicit Boundaries (Out-of-Scope)

  • Payment Gateway Integration: Actual credit card capture, bank transaction rails, or Stripe/PayPal double-spend settlements.
  • KYC / User Identity Checks: Core profile, bank account validation, background identity checks, or anti-fraud rating models.

Class Diagram & Entity Relationships

Entity structure highlighting CAS-compatible bidding data structures and observers:

Loading...
  • Auction: Container of current state, start/end dates, increments, reserve values, and active locks.
  • ProxyBid: Keeps a bidder's secret maximum limit, triggered to bid automatically against competitor actions.

Design Patterns & SOLID Principles

  • Observer Pattern: Used to decouple the core bid processor from peripheral notifications, audit trails, and live analytics dashboards.
  • Strategy Pattern: Enables swappable bidding rules (e.g., standard incremental vs. sealed-bid vs. Dutch auctions).
  • Single Responsibility Principle (SRP): Decouples BidProcessor (rules and synchronization) from Auction (state and schema).

Core Execution Workflows

Bidding Processing with Proxy Mechanics

  1. A bidder submits a bid via processBid.
  2. The processor validates that the auction is active and that the bid satisfies the minimal increment criteria.
  3. If an active competitor proxy bid is set on the auction:
    • If the new bid is under the proxy limit, the proxy holder automatically outbids the newcomer by one increment.
    • If the new bid exceeds the proxy limit, the proxy is invalidated/exhausted, and the newcomer takes the lead.
  4. The processor evaluates the time remaining. If it falls inside the sniping window, the end time shifts dynamically.
  5. Observers are updated in a decoupled non-blocking fashion.

Concurrency & Thread Safety Strategy

Securing correct, non-overlapping updates under thundering herd closing moments:

  • Per-Auction Locks: Using synchronized blocks (Java), thread locks (Python), or state-level locks (TypeScript) per individual auction to avoid globally blocking the processor.
  • Atomic Reference CAS: Using Atomic CAS techniques for lightweight state switches of currentHighestBid.
  • Volatile Time Boundaries: Ensuring end-time extensions are visible across all worker threads immediately via memory barriers (volatile fields).

Complete Clean Code Blueprint

Production-ready reference designs illustrating proxy bidding and anti-sniping execution in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

class Bid {
    private final String bidder;
    private final double amount;
    private final long timestamp;

    public Bid(String bidder, double amount, long timestamp) {
        this.bidder = bidder;
        this.amount = amount;
        this.timestamp = timestamp;
    }

    public String getBidder() { return bidder; }
    public double getAmount() { return amount; }
    public long getTimestamp() { return timestamp; }

    @Override
    public String toString() {
        return String.format("Bid{bidder='%s', amount=%.2f, time=%d}", bidder, amount, timestamp);
    }
}

class ProxyBid {
    private final String bidder;
    private final double maxAmount;

    public ProxyBid(String bidder, double maxAmount) {
        this.bidder = bidder;
        this.maxAmount = maxAmount;
    }

    public String getBidder() { return bidder; }
    public double getMaxAmount() { return maxAmount; }
}

interface BidObserver {
    void onBidAccepted(String auctionId, Bid bid);
    void onAuctionClosed(String auctionId, Bid winningBid, boolean reserveMet);
}

class Auction {
    private final String id;
    private final String itemName;
    private final double reservePrice;
    private final double startPrice;
    private final long antiSnipingWindowMillis;
    private final double bidIncrement;
    private final ReentrantLock lock = new ReentrantLock();
    
    private volatile long endTime;
    private Bid currentHighestBid;
    private ProxyBid activeProxyBid;
    private volatile boolean isClosed;

    public Auction(String id, String itemName, double reservePrice, double startPrice, long startTime, long endTime, long antiSnipingWindowMillis, double bidIncrement) {
        this.id = id;
        this.itemName = itemName;
        this.reservePrice = reservePrice;
        this.startPrice = startPrice;
        this.endTime = endTime;
        this.antiSnipingWindowMillis = antiSnipingWindowMillis;
        this.bidIncrement = bidIncrement;
        this.currentHighestBid = new Bid("System", startPrice, startTime);
        this.activeProxyBid = null;
        this.isClosed = false;
    }

    public String getId() { return id; }
    public String getItemName() { return itemName; }
    public double getReservePrice() { return reservePrice; }
    public double getStartPrice() { return startPrice; }
    public long getEndTime() { return endTime; }
    public void extendEndTime(long newEndTime) { this.endTime = newEndTime; }
    public long getAntiSnipingWindowMillis() { return antiSnipingWindowMillis; }
    public double getBidIncrement() { return bidIncrement; }
    public Bid getCurrentHighestBid() { return currentHighestBid; }
    public void setCurrentHighestBid(Bid bid) { this.currentHighestBid = bid; }
    public ProxyBid getActiveProxyBid() { return activeProxyBid; }
    public void setActiveProxyBid(ProxyBid proxyBid) { this.activeProxyBid = proxyBid; }
    public boolean isClosed() { return isClosed; }
    public void setClosed(boolean closed) { this.isClosed = closed; }
    public ReentrantLock getLock() { return lock; }
}

class BidProcessor {
    private final List<BidObserver> observers = new CopyOnWriteArrayList<>();
    private final Map<String, Auction> auctions = new ConcurrentHashMap<>();

    public void registerObserver(BidObserver observer) {
        observers.add(observer);
    }

    public void addAuction(Auction auction) {
        auctions.put(auction.getId(), auction);
    }

    public void processBid(String auctionId, String bidder, double amount) throws Exception {
        Auction auction = auctions.get(auctionId);
        if (auction == null) {
            throw new IllegalArgumentException("Auction not found");
        }

        auction.getLock().lock();
        try {
            long now = System.currentTimeMillis();
            if (auction.isClosed() || now > auction.getEndTime()) {
                throw new IllegalStateException("Auction is closed or ended");
            }

            Bid currentHighest = auction.getCurrentHighestBid();
            double minRequired = currentHighest.getAmount() + auction.getBidIncrement();
            if (amount < minRequired) {
                throw new IllegalArgumentException("Bid amount " + amount + " is lower than minimum required " + minRequired);
            }

            ProxyBid activeProxy = auction.getActiveProxyBid();
            if (activeProxy != null && !activeProxy.getBidder().equals(bidder)) {
                if (amount <= activeProxy.getMaxAmount()) {
                    // Proxy outbids newcomer
                    double autoAmount = Math.min(activeProxy.getMaxAmount(), amount + auction.getBidIncrement());
                    Bid autoBid = new Bid(activeProxy.getBidder(), autoAmount, now);
                    auction.setCurrentHighestBid(autoBid);
                    applyAntiSniping(auction, now);
                    notifyBidAccepted(auctionId, autoBid);
                } else {
                    // Newcomer outbids proxy
                    Bid newBid = new Bid(bidder, amount, now);
                    auction.setCurrentHighestBid(newBid);
                    auction.setActiveProxyBid(null); // Proxy beaten
                    applyAntiSniping(auction, now);
                    notifyBidAccepted(auctionId, newBid);
                }
            } else {
                Bid newBid = new Bid(bidder, amount, now);
                auction.setCurrentHighestBid(newBid);
                applyAntiSniping(auction, now);
                notifyBidAccepted(auctionId, newBid);
            }
        } finally {
            auction.getLock().unlock();
        }
    }

    public void registerProxyBid(String auctionId, String bidder, double maxAmount) {
        Auction auction = auctions.get(auctionId);
        if (auction == null) {
            throw new IllegalArgumentException("Auction not found");
        }

        auction.getLock().lock();
        try {
            long now = System.currentTimeMillis();
            if (auction.isClosed() || now > auction.getEndTime()) {
                throw new IllegalStateException("Auction is closed or ended");
            }

            Bid currentHighest = auction.getCurrentHighestBid();
            if (maxAmount <= currentHighest.getAmount()) {
                throw new IllegalArgumentException("Proxy max must be higher than current highest bid");
            }

            ProxyBid newProxy = new ProxyBid(bidder, maxAmount);
            ProxyBid activeProxy = auction.getActiveProxyBid();

            if (activeProxy == null) {
                auction.setActiveProxyBid(newProxy);
                if (!currentHighest.getBidder().equals(bidder)) {
                    double autoAmount = Math.min(maxAmount, currentHighest.getAmount() + auction.getBidIncrement());
                    Bid autoBid = new Bid(bidder, autoAmount, now);
                    auction.setCurrentHighestBid(autoBid);
                    notifyBidAccepted(auctionId, autoBid);
                }
            } else {
                if (bidder.equals(activeProxy.getBidder())) {
                    // Update own proxy max
                    auction.setActiveProxyBid(newProxy);
                } else {
                    // Competing proxies
                    if (maxAmount > activeProxy.getMaxAmount()) {
                        double autoAmount = Math.min(maxAmount, activeProxy.getMaxAmount() + auction.getBidIncrement());
                        Bid autoBid = new Bid(bidder, autoAmount, now);
                        auction.setCurrentHighestBid(autoBid);
                        auction.setActiveProxyBid(newProxy);
                        notifyBidAccepted(auctionId, autoBid);
                    } else {
                        double autoAmount = Math.min(activeProxy.getMaxAmount(), maxAmount + auction.getBidIncrement());
                        Bid autoBid = new Bid(activeProxy.getBidder(), autoAmount, now);
                        auction.setCurrentHighestBid(autoBid);
                        notifyBidAccepted(auctionId, autoBid);
                    }
                }
            }
        } finally {
            auction.getLock().unlock();
        }
    }

    private void applyAntiSniping(Auction auction, long now) {
        long timeLeft = auction.getEndTime() - now;
        if (timeLeft < auction.getAntiSnipingWindowMillis()) {
            long newEndTime = now + auction.getAntiSnipingWindowMillis();
            auction.extendEndTime(newEndTime);
            System.out.println("[AntiSniping] Extended auction " + auction.getId() + " by " + auction.getAntiSnipingWindowMillis() + "ms. New EndTime: " + newEndTime);
        }
    }

    private void notifyBidAccepted(String auctionId, Bid bid) {
        for (BidObserver obs : observers) {
            obs.onBidAccepted(auctionId, bid);
        }
    }

    public void closeAuction(String auctionId) {
        Auction auction = auctions.get(auctionId);
        if (auction == null) return;

        auction.getLock().lock();
        try {
            if (auction.isClosed()) return;
            auction.setClosed(true);

            Bid winningBid = auction.getCurrentHighestBid();
            boolean reserveMet = winningBid.getAmount() >= auction.getReservePrice();

            for (BidObserver obs : observers) {
                obs.onAuctionClosed(auctionId, winningBid, reserveMet);
            }
        } finally {
            auction.getLock().unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("=== STARTING AUCTION SYSTEM SIMULATION ===");
        BidProcessor processor = new BidProcessor();

        // Register logger observer
        processor.registerObserver(new BidObserver() {
            @Override
            public void onBidAccepted(String auctionId, Bid bid) {
                System.out.println("[OBSERVER] Bid accepted on " + auctionId + ": " + bid);
            }

            @Override
            public void onAuctionClosed(String auctionId, Bid winningBid, boolean reserveMet) {
                System.out.println("[OBSERVER] Auction " + auctionId + " CLOSED. Winner: " + winningBid + " | Reserve Met: " + reserveMet);
            }
        });

        long start = System.currentTimeMillis();
        long end = start + 2000; // 2 seconds auction duration
        Auction macbook = new Auction("auc-101", "Macbook Pro M4", 1500.0, 1000.0, start, end, 500L, 50.0);
        processor.addAuction(macbook);

        // 1. Threaded high-frequency bidding
        System.out.println("\n--- Simulating High Frequency Bids ---");
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        executor.submit(() -> {
            try {
                processor.processBid("auc-101", "Alice", 1100.0);
            } catch (Exception e) {
                System.out.println("Alice error: " + e.getMessage());
            }
        });

        executor.submit(() -> {
            try {
                processor.processBid("auc-101", "Bob", 1200.0);
            } catch (Exception e) {
                System.out.println("Bob error: " + e.getMessage());
            }
        });

        executor.submit(() -> {
            try {
                // Register a high proxy bid for Charlie
                processor.registerProxyBid("auc-101", "Charlie", 1800.0);
            } catch (Exception e) {
                System.out.println("Charlie error: " + e.getMessage());
            }
        });

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.SECONDS);

        // 2. Submit normal bid to trigger proxy response
        System.out.println("\n--- Alice outbidding up to Charlie's proxy ---");
        try {
            processor.processBid("auc-101", "Alice", 1400.0);
        } catch (Exception e) {
            System.out.println("Alice normal bid error: " + e.getMessage());
        }

        // 3. Last second bid to test anti-sniping extension
        System.out.println("\n--- Dynamic Sniping Trigger ---");
        long mockLastSecond = macbook.getEndTime() - 100; // inside 500ms sniping window
        System.out.println("Current end time: " + macbook.getEndTime() + ", Mock bid time: " + mockLastSecond);
        
        // Wait briefly to let the real clock approach the end time
        Thread.sleep(1500);
        try {
            System.out.println("Placing high bid from Dave to outbid Charlie...");
            processor.processBid("auc-101", "Dave", 1900.0);
        } catch (Exception e) {
            System.out.println("Dave bid error: " + e.getMessage());
        }

        // Close auction
        System.out.println("\n--- Closing Auction ---");
        Thread.sleep(1000);
        processor.closeAuction("auc-101");
        System.out.println("=== AUCTION SYSTEM SIMULATION COMPLETE ===");
    }
}

๐Ÿ’ฌ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...