Machine Coding Problem

Auction System

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

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.

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 ===");
    }
}