Machine Coding Problem

Ad-Serving Engine

maco60macoAlladsbid-selectionauction-logic
Commonly Asked By:GoogleTradeDeskMeta

Requirements & System Scope

Functional Scope (In-Scope)

  • Targeted Ad Matching: Filter eligible ad campaigns based on user context details (geo, device type, audience segments) using the Specification Pattern.
  • Second-Price Vickrey Auctions: Run real-time auctions where the winning advertiser pays the second-highest bid plus a $0.01 increment.
  • Atomic Budget Deductions: Manage campaign budgets in micro-cents to handle high-concurrency deductions accurately without rounding errors.
  • Frequency Capping Guards: Limit identical ad displays to a maximum of N impressions per user per day.

Explicit Boundaries (Out-of-Scope)

  • No Complex RTB OpenRTB Bid Protocols: Bypasses live external DSP/SSP bidding round-trips over open web networks.
  • No ML-Based Click-Through Rate (CTR) Predictors: Excludes predicting click probabilities using neural networks.

Class Diagram & Entity Relationships

Structural layout showing relationships between targeting engines, auctions, and campaign budgets:

Loading...
  • Targeting Filters: TargetingSpecification filters campaigns using composite boolean predicates.
  • Auction Solvers: AuctionStrategy runs second-price auction mechanics over candidate bids.

Design Patterns & SOLID Principles

  • Specification Pattern (Audience Targeting): Decoupling targeting logic (Geo requirements, Device rules) into clean specification classes allows engineers to expand targeting criteria without changing core auction engines, satisfying SRP.
  • Strategy Pattern (Auction Strategies): Encapsulating bid models (First-price vs Second-price auctions) makes it easy to switch or compare different auction systems, satisfying Open-Closed Principle.

Core Execution Workflows

Ad Request & Auction Loop

  1. Client triggers an ad load request: serveAd(userContext).
  2. Filter active campaigns against geo-targeting parameters and active frequency caps.
  3. If no campaigns match target specifications, return a default fallback ad.
  4. Run a second-price auction on the matching campaigns:
    1. Identify the highest bidder (the winner) and the second-highest bid.
    2. Calculate the price: price = secondBid + 0.01.
  5. Deduct the calculated price from the winning campaign's budget atomically (CAS in micro-cents).
  6. Render the winning creative and increment the user's frequency cap count.

Concurrency & Thread Safety Strategy

When thousands of concurrent users trigger page views and ad auctions simultaneously:

  • Atomic Budget Deductions: Manage budgets using atomic variables (e.g., Java's AtomicLong) in micro-cents to prevent race conditions during parallel checkout events.
  • Thread-Safe Impressions Store: Use `ConcurrentHashMap` structures to avoid corruption when tracking overlapping click telemetry.

Complete Clean Code Blueprint

Clean reference designs demonstrating second-price auctions in Java and Python:

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

enum DeviceType { MOBILE, DESKTOP, TABLET }

class UserContext {
    private final String userId;
    private final String geo;
    private final DeviceType deviceType;
    private final Set<String> interests = new HashSet<>();

    public UserContext(String userId, String geo, DeviceType deviceType, List<String> interests) {
        this.userId = userId;
        this.geo = geo;
        this.deviceType = deviceType;
        this.interests.addAll(interests);
    }

    public String getUserId() { return userId; }
    public String getGeo() { return geo; }
    public DeviceType getDeviceType() { return deviceType; }
    public Set<String> getInterests() { return interests; }
}

// Specification Pattern for Targeting Filter
interface TargetingSpecification {
    boolean isSatisfiedBy(UserContext context);
}

class GeoTargetingSpecification implements TargetingSpecification {
    private final Set<String> allowedGeos;
    public GeoTargetingSpecification(String... geos) {
        this.allowedGeos = new HashSet<>(Arrays.asList(geos));
    }
    @Override
    public boolean isSatisfiedBy(UserContext context) {
        return allowedGeos.isEmpty() || allowedGeos.contains(context.getGeo());
    }
}

class DeviceTargetingSpecification implements TargetingSpecification {
    private final Set<DeviceType> allowedDevices;
    public DeviceTargetingSpecification(DeviceType... devices) {
        this.allowedDevices = new HashSet<>(Arrays.asList(devices));
    }
    @Override
    public boolean isSatisfiedBy(UserContext context) {
        return allowedDevices.isEmpty() || allowedDevices.contains(context.getDeviceType());
    }
}

class AdCampaign {
    private final String id;
    private final double bidAmount; // in dollars
    private final AtomicLong budgetMicroCents;
    private final List<TargetingSpecification> targetSpecs = new ArrayList<>();
    private final String creativeUrl;

    public AdCampaign(String id, double bidAmount, double budget, String creativeUrl) {
        this.id = id;
        this.bidAmount = bidAmount;
        this.budgetMicroCents = new AtomicLong((long)(budget * 1_000_000.0));
        this.creativeUrl = creativeUrl;
    }

    public String getId() { return id; }
    public double getBidAmount() { return bidAmount; }
    public String getCreativeUrl() { return creativeUrl; }

    public void addSpecification(TargetingSpecification spec) {
        targetSpecs.add(spec);
    }

    public boolean matchesTarget(UserContext context) {
        for (TargetingSpecification spec : targetSpecs) {
            if (!spec.isSatisfiedBy(context)) return false;
        }
        return true;
    }

    public boolean tryDeductBudget(double cost) {
        long microCost = (long)(cost * 1_000_000.0);
        while (true) {
            long current = budgetMicroCents.get();
            if (current < microCost) return false;
            if (budgetMicroCents.compareAndSet(current, current - microCost)) {
                return true;
            }
        }
    }

    public double getRemainingBudget() {
        return budgetMicroCents.get() / 1_000_000.0;
    }
}

class FrequencyCapper {
    // Map of UserID -> (CampaignID -> ImpressionCount)
    private final Map<String, Map<String, Integer>> userImpressions = new ConcurrentHashMap<>();

    public boolean isCapReached(String userId, String campaignId, int maxImpressions) {
        Map<String, Integer> campaignCounts = userImpressions.get(userId);
        if (campaignCounts == null) return false;
        return campaignCounts.getOrDefault(campaignId, 0) >= maxImpressions;
    }

    public void recordImpression(String userId, String campaignId) {
        userImpressions.computeIfAbsent(userId, k -> new ConcurrentHashMap<>())
                       .merge(campaignId, 1, Integer::sum);
    }
}

// Strategy Pattern for Auction type
interface AuctionStrategy {
    AuctionResult runAuction(List<AdCampaign> campaigns);
}

class AuctionResult {
    private final AdCampaign winner;
    private final double cost;

    public AuctionResult(AdCampaign winner, double cost) {
        this.winner = winner;
        this.cost = cost;
    }

    public AdCampaign getWinner() { return winner; }
    public double getCost() { return cost; }
}

class SecondPriceVickreyAuction implements AuctionStrategy {
    @Override
    public AuctionResult runAuction(List<AdCampaign> campaigns) {
        if (campaigns.isEmpty()) return null;
        if (campaigns.size() == 1) {
            // Pay a reserve floor price of $0.01 or custom value
            return new AuctionResult(campaigns.get(0), 0.01);
        }

        // Sort campaign list descending by bid amount
        campaigns.sort((a, b) -> Double.compare(b.getBidAmount(), a.getBidAmount()));
        AdCampaign winner = campaigns.get(0);
        double secondBid = campaigns.get(1).getBidAmount();
        double finalCost = secondBid + 0.01; // Vickrey model: Second price + 1 penny

        return new AuctionResult(winner, finalCost);
    }
}

class AdServingEngine {
    private final List<AdCampaign> campaigns = new CopyOnWriteArrayList<>();
    private final FrequencyCapper frequencyCapper = new FrequencyCapper();
    private final AuctionStrategy auctionStrategy;
    private final int dailyCapLimit;

    public AdServingEngine(AuctionStrategy auctionStrategy, int dailyCapLimit) {
        this.auctionStrategy = auctionStrategy;
        this.dailyCapLimit = dailyCapLimit;
    }

    public void registerCampaign(AdCampaign campaign) {
        campaigns.add(campaign);
    }

    public String serveAd(UserContext context) {
        List<AdCampaign> eligibleCampaigns = new ArrayList<>();
        
        for (AdCampaign campaign : campaigns) {
            // 1. Evaluate targeting specifications
            if (!campaign.matchesTarget(context)) continue;

            // 2. Enforce frequency capping guard
            if (frequencyCapper.isCapReached(context.getUserId(), campaign.getId(), dailyCapLimit)) {
                System.out.println("Frequency limit reached for Campaign " + campaign.getId() + " and User " + context.getUserId());
                continue;
            }

            // 3. Verify budget exists
            if (campaign.getRemainingBudget() <= 0.0) continue;

            eligibleCampaigns.add(campaign);
        }

        if (eligibleCampaigns.isEmpty()) {
            return "creative_fallback.jpg"; // Default fallback creative
        }

        // 4. Run second-price Vickrey auction
        AuctionResult result = auctionStrategy.runAuction(eligibleCampaigns);
        if (result == null) return "creative_fallback.jpg";

        AdCampaign winner = result.getWinner();
        double cost = result.getCost();

        // 5. Try atomic budget deduction
        if (winner.tryDeductBudget(cost)) {
            frequencyCapper.recordImpression(context.getUserId(), winner.getId());
            System.out.println("Served Ad: " + winner.getId() + " to " + context.getUserId() 
                               + ". Winner bid: $" + winner.getBidAmount() + ". Final Price Paid: $" + String.format("%.2f", cost));
            return winner.getCreativeUrl();
        } else {
            // Recurse / fallback if winner ran out of budget in a race condition
            System.out.println("Winner " + winner.getId() + " failed budget check due to concurrent checkout. Retrying...");
            return serveAd(context);
        }
    }
}

// Complete Simulation
public class Main {
    public static void main(String[] args) {
        AdServingEngine engine = new AdServingEngine(new SecondPriceVickreyAuction(), 2);

        // Define campaigns with different bid sizes
        AdCampaign c1 = new AdCampaign("C-NIKE", 2.50, 10.00, "nike_shoes.jpg");
        c1.addSpecification(new GeoTargetingSpecification("US", "CA"));
        c1.addSpecification(new DeviceTargetingSpecification(DeviceType.MOBILE));

        AdCampaign c2 = new AdCampaign("C-ADIDAS", 1.80, 15.00, "adidas_boost.jpg");
        c2.addSpecification(new GeoTargetingSpecification("US"));

        AdCampaign c3 = new AdCampaign("C-PUMA", 3.00, 0.50, "puma_wild.jpg"); // puma high bid but very low budget
        c3.addSpecification(new GeoTargetingSpecification("US"));

        engine.registerCampaign(c1);
        engine.registerCampaign(c2);
        engine.registerCampaign(c3);

        UserContext user = new UserContext("U-Mark", "US", DeviceType.MOBILE, Arrays.asList("sports", "running"));

        // 1. Serving first ad - expect Puma to win but pay second price ($2.51)
        System.out.println("--- Ad Request 1 ---");
        engine.serveAd(user);

        // 2. Puma budget is now $0.50 - $2.51 = failed (insufficient budget)
        // Next request - expect Nike to win and pay Adidas's price ($1.81)
        System.out.println("\n--- Ad Request 2 ---");
        engine.serveAd(user);

        // 3. Nike wins again, pays Adidas ($1.81)
        System.out.println("\n--- Ad Request 3 ---");
        engine.serveAd(user);

        // 4. Frequency cap check (Nike has been shown 2 times, limit is 2)
        // Nike should be blocked. Expect Adidas to win and pay reserve limit ($0.01)
        System.out.println("\n--- Ad Request 4 (Capping verification) ---");
        engine.serveAd(user);
    }
}

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