Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/72d32251168f66e77fdc36d494372fec to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/72d32251168f66e77fdc36d494372fec to your computer and use it in GitHub Desktop.
Microsoft Ads Team Interview Guide

Microsoft Ads Team Interview Guide

Table of Contents


1. Ad Tech Fundamentals

What is an Ad Impression?

Definition: An impression is counted when an ad is successfully rendered or becomes viewable on the page.

Key Point: Not just loaded into the DOM, but actually visible to the user.

Served vs Viewable Impressions

Type Definition When Counted
Served Impression Ad request was fulfilled and HTML/creative was delivered When ad markup is inserted into DOM
Viewable Impression Ad meets viewability criteria (IAB standards) When 50%+ of ad pixels are visible for 1+ second

IAB Viewability Standard:

  • Display ads: ≥50% of pixels visible for ≥1 continuous second
  • Video ads: ≥50% of pixels visible for ≥2 continuous seconds

Counting Impressions in SPAs

Challenge: SPAs don't have traditional page loads, so you need to track impressions programmatically.

Solution: Use IntersectionObserver API

function trackImpression(adElement, adId) {
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
          // Start visibility timer
          const timer = setTimeout(() => {
            // Fire impression event
            sendImpression(adId);
            observer.disconnect();
          }, 1000); // 1 second visibility threshold
          
          // Store timer to clear if element becomes hidden
          entry.target.dataset.impressionTimer = timer;
        } else {
          // Element no longer visible, clear timer
          const timer = entry.target.dataset.impressionTimer;
          if (timer) clearTimeout(timer);
        }
      });
    },
    { threshold: 0.5 } // 50% visibility threshold
  );
  
  observer.observe(adElement);
  return observer;
}

Key Points for Interview:

  • Use threshold: 0.5 for 50% visibility
  • Add time-based check (1 second) to meet IAB standards
  • Clean up observer when impression is counted
  • Handle cases where ad scrolls out of view before timer completes

2. Click Tracking

Reliable Event Handling

❌ Bad Approach:

// Don't rely solely on 'click' event
adElement.addEventListener('click', trackClick);

✅ Good Approach:

// Use mousedown/pointerdown for earlier capture
adElement.addEventListener('pointerdown', (e) => {
  const clickData = {
    adId: adElement.dataset.adId,
    timestamp: Date.now(),
    coordinates: { x: e.clientX, y: e.clientY },
    pointerType: e.pointerType // 'mouse', 'pen', 'touch'
  };
  
  // Send immediately before navigation
  navigator.sendBeacon('/track-click', JSON.stringify(clickData));
});

Why pointerdown instead of click?

  • Fires earlier in the event chain
  • Navigation might happen before click fires
  • Works across mouse, touch, and pen inputs
  • Less likely to be lost during page unload

Preventing Data Loss

Problem: User navigates away before tracking request completes.

Solution: Use navigator.sendBeacon()

function trackClick(adId, metadata) {
  const payload = JSON.stringify({
    adId,
    timestamp: Date.now(),
    ...metadata
  });
  
  // sendBeacon queues request even if page unloads
  const success = navigator.sendBeacon('/track-click', payload);
  
  if (!success) {
    // Fallback: queue in localStorage/IndexedDB
    queueOfflineEvent('click', payload);
  }
}

Benefits of sendBeacon:

  • Non-blocking (doesn't delay navigation)
  • Browser guarantees delivery even after page unload
  • POST request with configurable payload
  • Returns boolean indicating if queued successfully

3. Performance Optimization

Loading Ads Without Hurting Performance

Expected Points:

  1. Async Loading
// Load ad SDK asynchronously
const script = document.createElement('script');
script.src = 'https://ads.example.com/sdk.js';
script.async = true;
script.defer = true;
document.head.appendChild(script);
  1. Lazy Loading
// Only load ads when they're about to enter viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadAd(entry.target);
      observer.unobserve(entry.target);
    }
  });
}, { rootMargin: '200px' }); // Pre-load 200px before visible
  1. Code Splitting
// Dynamic import for ad formats
async function loadVideoAd(container) {
  const { VideoAdRenderer } = await import('./video-ad-renderer');
  new VideoAdRenderer(container).render();
}
  1. No Render Blocking
<!-- ❌ Bad: Blocks rendering -->
<script src="/ads.js"></script>

<!-- ✅ Good: Non-blocking -->
<script src="/ads.js" async></script>
  1. Prevent Layout Shifts (CLS)
/* Reserve space before ad loads */
.ad-container {
  width: 300px;
  height: 250px;
  background: #f0f0f0; /* Placeholder background */
}

Cumulative Layout Shift (CLS)

Why Microsoft Cares:

  • CLS hurts SEO rankings (Core Web Vitals)
  • Bad user experience → lower engagement
  • Lower engagement = lower ad revenue

❌ Bad Example:

<!-- Ad loads and pushes content down -->
<div id="ad"></div>
<main>Content here...</main>

✅ Good Example:

<!-- Pre-allocated space -->
<div id="ad" style="width: 300px; height: 250px;">
  <!-- Ad loads into fixed space -->
</div>
<main>Content here...</main>

CSS Approach:

.ad-slot {
  width: 300px;
  height: 250px;
  min-height: 250px; /* Prevents collapse */
  background: #f5f5f5;
  position: relative;
}

/* Responsive ads */
.ad-slot-responsive {
  aspect-ratio: 16 / 9;
  width: 100%;
  max-width: 728px;
}

Interview Answer Template:

"CLS is critical for ads because layout shifts directly impact user experience and SEO. We prevent this by pre-allocating space with fixed dimensions before the ad loads. For responsive ads, we use aspect-ratio CSS or calculate dimensions based on viewport size. This ensures content doesn't jump when ads render, which improves both Core Web Vitals scores and user engagement—both tied to revenue."


4. Building Ad Components

React AdSlot Component

Basic Implementation:

import { useEffect, useRef, useState } from 'react';

function AdSlot({ adId, width = 300, height = 250, onImpression }) {
  const ref = useRef(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const impressionFired = useRef(false);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && !impressionFired.current) {
          // Load ad creative
          loadAd(adId, element).then(() => {
            setIsLoaded(true);
          });

          // Start viewability timer
          setTimeout(() => {
            if (!impressionFired.current) {
              impressionFired.current = true;
              onImpression?.(adId);
            }
          }, 1000);

          observer.disconnect();
        }
      },
      { threshold: 0.5 }
    );

    observer.observe(element);

    return () => {
      observer.disconnect();
    };
  }, [adId, onImpression]);

  return (
    <div
      ref={ref}
      className="ad-slot"
      style={{ width, height }}
      data-ad-id={adId}
    >
      {!isLoaded && <div className="ad-placeholder">Loading...</div>}
    </div>
  );
}

Preventing Double-Counting

Problem: Impressions fire multiple times due to re-renders or component remounts.

Solutions:

  1. Use Ref to Track State
const impressionFired = useRef(false);

useEffect(() => {
  if (!impressionFired.current) {
    fireImpression(adId);
    impressionFired.current = true;
  }
}, [adId]);
  1. Unique Impression IDs
const impressionId = `${adId}-${Date.now()}-${Math.random()}`;
const fired = new Set(); // Global or context-based

if (!fired.has(impressionId)) {
  fireImpression(adId, impressionId);
  fired.add(impressionId);
}
  1. Server-Side Deduplication
// Send client-generated UUID
const clientId = crypto.randomUUID();
sendImpression({ adId, clientId, timestamp: Date.now() });

// Server deduplicates based on clientId + adId + time window

Route Change Handling

Problem: In SPAs, route changes don't reload ads automatically.

Solution: Listen to route changes and refresh ads

import { useLocation } from 'react-router-dom';

function AdSlot({ adId }) {
  const location = useLocation();
  const ref = useRef(null);
  const [key, setKey] = useState(0);

  useEffect(() => {
    // Refresh ad on route change
    setKey(prev => prev + 1);
    
    // Clean up previous ad
    return () => {
      cleanupAd(ref.current);
    };
  }, [location.pathname]);

  return <div ref={ref} key={key} data-ad-id={adId} />;
}

Advanced Pattern with Ad Manager:

// AdManager context
const AdManagerContext = createContext();

function AdManager({ children }) {
  const location = useLocation();
  const [adSlots, setAdSlots] = useState(new Map());

  useEffect(() => {
    // Refresh all ads on route change
    adSlots.forEach((slot, id) => {
      refreshAd(id, slot);
    });
  }, [location.pathname]);

  const registerSlot = (id, element) => {
    setAdSlots(prev => new Map(prev).set(id, element));
  };

  const unregisterSlot = (id) => {
    setAdSlots(prev => {
      const next = new Map(prev);
      next.delete(id);
      return next;
    });
  };

  return (
    <AdManagerContext.Provider value={{ registerSlot, unregisterSlot }}>
      {children}
    </AdManagerContext.Provider>
  );
}

5. SPA-Specific Ad Problems

Route Changes in React

Key Issues:

  • Ads don't automatically reload
  • Old ad references remain in memory
  • Impression tracking breaks
  • Event listeners persist

Solution Pattern:

function AdContainer() {
  const location = useLocation();
  const adRefs = useRef(new Map());

  useEffect(() => {
    // Clean up old ads
    adRefs.current.forEach((ad) => {
      ad.destroy(); // Remove listeners, clear timers
    });
    adRefs.current.clear();

    // Request new ads for current page
    refreshAds();

    return () => {
      // Cleanup on unmount
      adRefs.current.forEach((ad) => ad.destroy());
    };
  }, [location.pathname]);

  return <div className="ad-container">{/* Ad slots */}</div>;
}

Memory Leak Prevention

Common Sources of Leaks:

  • IntersectionObserver not disconnected
  • Event listeners not removed
  • Timers not cleared
  • Global state not cleaned

Comprehensive Cleanup Pattern:

function AdSlot({ adId }) {
  const ref = useRef(null);
  const observerRef = useRef(null);
  const timersRef = useRef([]);

  useEffect(() => {
    const element = ref.current;
    
    // Setup observer
    observerRef.current = new IntersectionObserver(callback);
    observerRef.current.observe(element);

    // Setup timers
    const timer1 = setTimeout(() => {}, 1000);
    const timer2 = setInterval(() => {}, 5000);
    timersRef.current = [timer1, timer2];

    // Setup event listeners
    const handleClick = () => {};
    element.addEventListener('click', handleClick);

    // Cleanup function
    return () => {
      // Disconnect observer
      observerRef.current?.disconnect();
      
      // Clear all timers
      timersRef.current.forEach(clearTimeout);
      timersRef.current.forEach(clearInterval);
      
      // Remove event listeners
      element?.removeEventListener('click', handleClick);
      
      // Clear references
      observerRef.current = null;
      timersRef.current = [];
    };
  }, [adId]);

  return <div ref={ref} />;
}

6. Event Tracking at Scale

Batching Strategy

Problem: Sending millions of individual events blocks the main thread and overloads servers.

Solution: Batch events and flush periodically

class EventTracker {
  constructor() {
    this.queue = [];
    this.maxBatchSize = 20;
    this.flushInterval = 5000; // 5 seconds
    this.startAutoFlush();
  }

  track(event) {
    this.queue.push({
      ...event,
      timestamp: Date.now(),
      clientId: this.getClientId()
    });

    // Flush if batch is full
    if (this.queue.length >= this.maxBatchSize) {
      this.flush();
    }
  }

  async flush() {
    if (this.queue.length === 0) return;

    const batch = this.queue.splice(0, this.maxBatchSize);
    
    try {
      // Non-blocking send
      if (navigator.sendBeacon) {
        navigator.sendBeacon('/events', JSON.stringify(batch));
      } else {
        // Fallback
        fetch('/events', {
          method: 'POST',
          body: JSON.stringify(batch),
          keepalive: true
        });
      }
    } catch (error) {
      // Retry or queue for offline
      this.handleFailure(batch);
    }
  }

  startAutoFlush() {
    setInterval(() => this.flush(), this.flushInterval);
    
    // Flush on page unload
    window.addEventListener('beforeunload', () => this.flush());
  }

  handleFailure(batch) {
    // Store in IndexedDB for retry
    this.storeOffline(batch);
  }
}

// Usage
const tracker = new EventTracker();
tracker.track({ type: 'impression', adId: '123' });
tracker.track({ type: 'click', adId: '456' });

Throttle/Debounce for High-Frequency Events:

// Throttle: Execute at most once per interval
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Track scroll events (throttled)
const trackScroll = throttle((scrollY) => {
  tracker.track({ type: 'scroll', position: scrollY });
}, 1000);

window.addEventListener('scroll', () => trackScroll(window.scrollY));

Offline Queue

Using IndexedDB for Reliability:

class OfflineEventQueue {
  constructor() {
    this.dbName = 'AdEventsDB';
    this.storeName = 'events';
    this.initDB();
  }

  async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { 
            keyPath: 'id', 
            autoIncrement: true 
          });
        }
      };
    });
  }

  async add(event) {
    const transaction = this.db.transaction([this.storeName], 'readwrite');
    const store = transaction.objectStore(this.storeName);
    return store.add(event);
  }

  async getAll() {
    const transaction = this.db.transaction([this.storeName], 'readonly');
    const store = transaction.objectStore(this.storeName);
    return new Promise((resolve) => {
      const request = store.getAll();
      request.onsuccess = () => resolve(request.result);
    });
  }

  async clear() {
    const transaction = this.db.transaction([this.storeName], 'readwrite');
    const store = transaction.objectStore(this.storeName);
    return store.clear();
  }

  async syncWhenOnline() {
    if (!navigator.onLine) return;

    const events = await this.getAll();
    if (events.length === 0) return;

    try {
      await fetch('/events/bulk', {
        method: 'POST',
        body: JSON.stringify(events)
      });
      await this.clear();
    } catch (error) {
      console.error('Sync failed:', error);
    }
  }
}

// Auto-sync when back online
window.addEventListener('online', () => {
  offlineQueue.syncWhenOnline();
});

Web Worker for Background Processing:

// event-worker.js
self.addEventListener('message', (e) => {
  const { type, data } = e.data;
  
  if (type === 'track') {
    // Process event in background
    const processed = processEvent(data);
    self.postMessage({ type: 'processed', data: processed });
  }
});

// Main thread
const worker = new Worker('event-worker.js');
worker.postMessage({ type: 'track', data: eventData });

Interview-Winning Talking Points:

  • "We don't block the main thread"
  • "Fire-and-forget telemetry with retry logic"
  • "Batching reduces network overhead by 95%"
  • "IndexedDB ensures zero data loss even when offline"

7. Fraud Detection & Invalid Traffic

Frontend Detection Techniques

Key Signals to Track:

class FraudDetector {
  detectSuspiciousClick(event, adElement) {
    const signals = {
      // 1. Time-to-click (too fast = bot)
      timeToClick: Date.now() - adElement.dataset.renderTime,
      
      // 2. Click coordinates
      coordinates: { x: event.clientX, y: event.clientY },
      
      // 3. Repeated clicks at same position
      isSamePosition: this.checkDuplicatePosition(event),
      
      // 4. User agent
      userAgent: navigator.userAgent,
      
      // 5. Ad visibility
      isVisible: this.checkVisibility(adElement),
      
      // 6. Tab visibility
      isTabActive: !document.hidden,
      
      // 7. Pointer movement
      hasPointerMovement: this.hasRecentPointerActivity(),
      
      // 8. Touch vs mouse
      inputType: event.pointerType || 'unknown'
    };

    return this.calculateFraudScore(signals);
  }

  checkDuplicatePosition(event) {
    const key = `${event.clientX},${event.clientY}`;
    const lastClick = this.clickHistory.get(key);
    
    if (lastClick && Date.now() - lastClick < 1000) {
      return true; // Same position within 1 second
    }
    
    this.clickHistory.set(key, Date.now());
    return false;
  }

  checkVisibility(element) {
    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= window.innerHeight &&
      rect.right <= window.innerWidth &&
      rect.width > 0 &&
      rect.height > 0
    );
  }

  hasRecentPointerActivity() {
    // Track if user has moved mouse recently
    return Date.now() - this.lastPointerMove < 5000;
  }

  calculateFraudScore(signals) {
    let score = 0;

    // Too fast click (< 100ms)
    if (signals.timeToClick < 100) score += 30;

    // Duplicate position
    if (signals.isSamePosition) score += 25;

    // Hidden ad
    if (!signals.isVisible) score += 40;

    // Background tab
    if (!signals.isTabActive) score += 20;

    // No pointer movement
    if (!signals.hasPointerMovement) score += 15;

    return {
      score, // 0-100
      signals,
      isSuspicious: score > 50
    };
  }
}

Heuristics & Signals

1. Visibility API Usage:

function trackAdVisibility(adId) {
  // Detect if page is in background
  document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
      // Ad not viewable in background tab
      markAsNonViewable(adId);
    }
  });
}

2. Pointer Movement Tracking:

let lastPointerMove = 0;
let pointerPath = [];

document.addEventListener('pointermove', (e) => {
  lastPointerMove = Date.now();
  pointerPath.push({ x: e.clientX, y: e.clientY, t: Date.now() });
  
  // Keep only last 10 points
  if (pointerPath.length > 10) pointerPath.shift();
});

function hasNaturalPointerMovement() {
  if (pointerPath.length < 3) return false;
  
  // Check for natural human movement (not perfectly straight line)
  const variance = calculatePathVariance(pointerPath);
  return variance > THRESHOLD;
}

3. Click Pattern Analysis:

class ClickPatternAnalyzer {
  constructor() {
    this.clicks = [];
  }

  recordClick(event) {
    this.clicks.push({
      x: event.clientX,
      y: event.clientY,
      timestamp: Date.now(),
      target: event.target.dataset.adId
    });

    // Check for suspicious patterns
    if (this.detectRapidClicking()) {
      this.flagAsSuspicious();
    }
  }

  detectRapidClicking() {
    const recentClicks = this.clicks.filter(
      c => Date.now() - c.timestamp < 5000
    );
    
    // More than 5 clicks in 5 seconds = suspicious
    return recentClicks.length > 5;
  }

  detectClickFarm() {
    // Same coordinates, different ads
    const positions = new Map();
    
    this.clicks.forEach(click => {
      const key = `${click.x},${click.y}`;
      if (!positions.has(key)) {
        positions.set(key, []);
      }
      positions.get(key).push(click.target);
    });

    // Multiple ads clicked at exact same position
    for (const [pos, ads] of positions) {
      if (new Set(ads).size > 3) return true;
    }
    
    return false;
  }
}

Interview Answer Template:

"For fraud detection on the frontend, I'd track multiple signals: time-to-click to catch bots that click instantly, coordinate tracking to detect click farms hitting the same pixel, visibility checks using IntersectionObserver to ensure ads are actually viewable, tab visibility via the Page Visibility API, and pointer movement heuristics to distinguish human behavior from automated scripts. These signals get combined into a fraud score that's sent to the backend for final validation."


8. Security Considerations

Attack Vectors

1. XSS (Cross-Site Scripting)

Third-party ad creatives can inject malicious scripts:

<!-- Malicious ad creative -->
<div>
  <img src="x" onerror="alert('XSS')" />
  <script>stealCookies()</script>
</div>

2. Clickjacking

Transparent iframes overlay on ads to steal clicks:

<!-- Attacker's page -->
<iframe 
  src="https://example.com/ad" 
  style="opacity: 0; position: absolute; top: 0;"
></iframe>
<button>Click me!</button> <!-- Actually clicks ad -->

3. Malicious Creatives

Ad content that:

  • Redirects to phishing sites
  • Auto-downloads malware
  • Crypto-mining scripts
  • Data exfiltration

4. Ad Injection

Malware injects unwanted ads into legitimate sites.

Mitigation Strategies

1. Sandbox Iframes

<!-- Isolate ad content -->
<iframe 
  sandbox="allow-scripts allow-same-origin"
  src="/ad-creative.html"
  style="width: 300px; height: 250px;"
></iframe>

Sandbox Attributes:

  • allow-scripts: Allow JavaScript (needed for ads)
  • allow-same-origin: Allow same-origin access (careful!)
  • allow-top-navigation: Allow redirects (often needed)
  • Omit allow-forms if forms aren't needed

2. Content Security Policy (CSP)

<meta 
  http-equiv="Content-Security-Policy" 
  content="
    default-src 'self';
    script-src 'self' https://trusted-ad-cdn.com;
    img-src 'self' https: data:;
    frame-src https://trusted-ad-server.com;
    connect-src 'self' https://analytics.example.com;
  "
>

3. Subresource Integrity (SRI)

<!-- Verify third-party scripts haven't been tampered with -->
<script 
  src="https://ads-cdn.com/sdk.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>

4. Input Sanitization

// Sanitize ad creative before rendering
function sanitizeAdCreative(html) {
  const temp = document.createElement('div');
  temp.textContent = html; // Converts to text, strips tags
  return temp.innerHTML;
}

// Or use DOMPurify
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML);

5. Feature Policy / Permissions Policy

<!-- Restrict what ads can do -->
<iframe 
  src="/ad.html"
  allow="
    geolocation 'none';
    microphone 'none';
    camera 'none';
    payment 'none';
  "
></iframe>

6. Monitoring & Validation

// Monitor for suspicious behavior
class AdSecurityMonitor {
  monitorAdBehavior(iframe) {
    // Detect redirect attempts
    iframe.addEventListener('load', () => {
      if (iframe.contentWindow.location !== originalUrl) {
        this.flagSuspiciousAd(iframe);
      }
    });

    // Monitor resource loading
    this.observeResources(iframe);
    
    // Check for crypto-mining (high CPU usage)
    this.monitorCPUUsage();
  }

  observeResources(iframe) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (this.isSuspiciousDomain(entry.name)) {
          this.blockResource(entry.name);
          this.reportViolation(iframe, 'suspicious-resource');
        }
      }
    });
    observer.observe({ entryTypes: ['resource'] });
  }

  monitorCPUUsage() {
    // Detect crypto-mining or excessive CPU usage
    setInterval(() => {
      if (this.getCPUUsage() > 80) {
        this.flagHighCPUAd();
      }
    }, 5000);
  }
}

Interview Answer Template:

"Ads are a major attack surface because we're loading third-party content. We mitigate this through multiple layers: sandboxed iframes to isolate ad content, CSP headers to restrict what scripts can run, SRI to verify third-party SDK integrity, and runtime monitoring to detect malicious behavior like unauthorized redirects or crypto-mining. We also maintain an allowlist of trusted ad servers and validate all creatives before serving."


9. Bundle Optimization

Code Splitting Strategies

Why This Matters for Ads:

  • Ads SDK runs on every page
  • Must be tiny (~10-20KB gzipped max)
  • Users shouldn't wait for ads to see content

1. Separate Ads Bundle

// webpack.config.js
module.exports = {
  entry: {
    main: './src/index.js',
    ads: './src/ads/index.js' // Separate bundle
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        ads: {
          test: /[\\/]src[\\/]ads[\\/]/,
          name: 'ads',
          chunks: 'all'
        }
      }
    }
  }
};

2. Dynamic Import by Ad Type

// Load only what's needed
async function renderAd(adConfig) {
  const { type } = adConfig;
  
  switch (type) {
    case 'video':
      const { VideoAdRenderer } = await import(
        /* webpackChunkName: "video-ads" */ 
        './renderers/video'
      );
      return new VideoAdRenderer(adConfig);
      
    case 'carousel':
      const { CarouselAdRenderer } = await import(
        /* webpackChunkName: "carousel-ads" */
        './renderers/carousel'
      );
      return new CarouselAdRenderer(adConfig);
      
    case 'native':
      const { NativeAdRenderer } = await import(
        /* webpackChunkName: "native-ads" */
        './renderers/native'
      );
      return new NativeAdRenderer(adConfig);
      
    default:
      // Display ads are inline (most common)
      return new DisplayAdRenderer(adConfig);
  }
}

3. Tree Shaking

// ads-sdk/index.js - Export only what's needed
export { trackImpression } from './tracking/impression';
export { trackClick } from './tracking/click';
// Don't export everything: export * from './utils'; ❌

// Consumer code
import { trackImpression } from 'ads-sdk';
// Only impression tracking code is bundled

4. Lazy Load Ad SDK

// Core app loads first
window.addEventListener('load', () => {
  // Load ads after page is interactive
  requestIdleCallback(() => {
    import('./ads-sdk').then(({ initAds }) => {
      initAds();
    });
  });
});

5. Format-Specific Loading

class AdManager {
  async loadAdFormat(format) {
    // Cache loaded formats
    if (this.loadedFormats.has(format)) {
      return this.loadedFormats.get(format);
    }

    const module = await this.importFormat(format);
    this.loadedFormats.set(format, module);
    return module;
  }

  async importFormat(format) {
    const formatMap = {
      display: () => import('./formats/display'),
      video: () => import('./formats/video'),
      native: () => import('./formats/native'),
      interstitial: () => import('./formats/interstitial')
    };

    return formatMap[format]?.() || formatMap.display();
  }
}

Caching Patterns

1. Aggressive Caching

// Service Worker for ad SDK
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('ads-sdk-v1').then((cache) => {
      return cache.addAll([
        '/ads-sdk.js',
        '/ads-core.js'
      ]);
    })
  );
});

// Cache first, network fallback
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/ads-sdk')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request);
      })
    );
  }
});

2. Versioned Assets

<!-- Content hash in filename for cache busting -->
<script src="/ads-sdk.a3f2b1c.js"></script>

3. CDN + Cache Headers

// Server configuration
app.get('/ads-sdk.js', (req, res) => {
  res.set({
    'Cache-Control': 'public, max-age=31536000, immutable',
    'Content-Type': 'application/javascript'
  });
  res.sendFile('ads-sdk.js');
});

4. Preload Critical Resources

<!-- Preload ads SDK during idle time -->
<link rel="preload" href="/ads-sdk.js" as="script">
<link rel="dns-prefetch" href="https://ads-cdn.example.com">
<link rel="preconnect" href="https://ads-cdn.example.com">

Bundle Size Optimization Checklist:

  • ✅ Separate bundle for ads code
  • ✅ Dynamic imports for ad formats
  • ✅ Tree shaking enabled
  • ✅ Minification + compression (gzip/brotli)
  • ✅ Remove unused dependencies
  • ✅ Use CDN for third-party libraries
  • ✅ Code split by route and feature
  • ✅ Lazy load non-critical code

Interview Answer Template:

"For ads bundle optimization, I'd create a separate bundle for ads code that's loaded asynchronously after the main app. We'd use dynamic imports to load only the ad format needed—for example, video ads only load when a video slot appears. Tree shaking removes unused code, and we'd serve everything through a CDN with aggressive caching (1 year max-age with content hashing). The goal is to keep the initial ads SDK under 15KB gzipped so it never impacts page load performance."


10. Key Metrics

Business Metrics

1. CTR (Click-Through Rate)

CTR = (Clicks / Impressions) × 100%
  • Good CTR: 0.5% - 2% for display ads
  • Indicates: Ad relevance and user engagement
  • Impact: Higher CTR = higher revenue

2. CPM (Cost Per Mille)

CPM = (Cost / Impressions) × 1000
  • Typical Range: $1 - $10 for display ads
  • Indicates: Revenue per 1000 impressions
  • Microsoft's Focus: Premium CPMs through quality placements

3. Fill Rate

Fill Rate = (Filled Ad Requests / Total Ad Requests) × 100%
  • Target: >90%
  • Indicates: How often ads are available to show
  • Impact: Low fill rate = lost revenue

4. Viewability

Viewability = (Viewable Impressions / Measured Impressions) × 100%
  • Industry Standard: >70%
  • IAB Definition: 50% of pixels visible for 1+ second
  • Impact: Higher viewability = higher CPMs

5. eCPM (Effective CPM)

eCPM = (Total Revenue / Total Impressions) × 1000
  • Indicates: Actual revenue per 1000 impressions
  • Used for: Comparing different ad formats/placements

Performance Metrics

1. CLS (Cumulative Layout Shift)

CLS = Σ(Impact Fraction × Distance Fraction)
  • Target: <0.1 (Good), <0.25 (Needs Improvement)
  • Why Critical: Core Web Vital, affects SEO
  • Ad Impact: Ads are #1 cause of high CLS

2. TTI (Time to Interactive)

Time when page becomes fully interactive
  • Target: <3.8s on mobile
  • Ad Impact: Heavy ad scripts delay TTI
  • Optimization: Async loading, code splitting

3. FID / INP (First Input Delay / Interaction to Next Paint)

FID: Time between first user interaction and browser response
INP: Responsiveness metric (replacing FID in 2024)
  • Target: FID <100ms, INP <200ms
  • Ad Impact: Blocking scripts increase FID/INP
  • Fix: Non-blocking ad loading

4. LCP (Largest Contentful Paint)

Time when largest element becomes visible
  • Target: <2.5s
  • Ad Impact: Large ads can be LCP element
  • Fix: Optimize ad image loading

Tracking Implementation:

// Track business metrics
class AdMetrics {
  constructor() {
    this.impressions = 0;
    this.clicks = 0;
    this.viewableImpressions = 0;
  }

  trackImpression(adId, isViewable) {
    this.impressions++;
    if (isViewable) this.viewableImpressions++;
    
    // Send to analytics
    this.send({
      type: 'impression',
      adId,
      viewable: isViewable,
      timestamp: Date.now()
    });
  }

  trackClick(adId) {
    this.clicks++;
    this.send({
      type: 'click',
      adId,
      timestamp: Date.now()
    });
  }

  getCTR() {
    return (this.clicks / this.impressions) * 100;
  }

  getViewability() {
    return (this.viewableImpressions / this.impressions) * 100;
  }
}

// Track performance metrics
class PerformanceMetrics {
  trackCLS() {
    let clsValue = 0;
    
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          clsValue += entry.value;
        }
      }
    }).observe({ type: 'layout-shift', buffered: true });

    return clsValue;
  }

  trackINP() {
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        const duration = entry.processingEnd - entry.processingStart;
        this.send({
          metric: 'INP',
          value: duration,
          entryType: entry.name
        });
      }
    }).observe({ type: 'event', buffered: true });
  }
}

Interview Talking Points:

  • "CLS is critical because layout shifts hurt both user experience and SEO, directly impacting revenue"
  • "We optimize for viewability because viewable impressions command 2-3x higher CPMs"
  • "TTI matters because slow pages increase bounce rate, reducing total impressions"
  • "I monitor INP to ensure ads don't block user interactions"

11. System Design Question

Client-Side Ad Rendering System

Problem Statement:

"Design a client-side ad rendering system that handles multiple ad formats, tracks impressions and clicks, optimizes performance, and scales to millions of page views."

High-Level Architecture:

┌─────────────────────────────────────────────────────────┐
│                    Page Application                      │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  ┌────────────┐  ┌─────────────┐  ┌──────────────┐     │
│  │  AdSlot 1  │  │  AdSlot 2   │  │  AdSlot 3    │     │
│  └─────┬──────┘  └──────┬──────┘  └──────┬───────┘     │
│        │                │                 │              │
│        └────────────────┴─────────────────┘              │
│                         │                                │
│                ┌────────▼────────┐                       │
│                │   Ad Manager    │                       │
│                └────────┬────────┘                       │
│                         │                                │
│        ┌────────────────┼────────────────┐              │
│        │                │                │              │
│  ┌─────▼──────┐  ┌─────▼──────┐  ┌─────▼──────┐       │
│  │ Visibility │  │  Renderer  │  │  Tracker   │       │
│  │  Manager   │  │  Manager   │  │  Manager   │       │
│  └────────────┘  └────────────┘  └────────────┘       │
│                                                          │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
            ┌─────────────────────────┐
            │   Ad Server / Backend   │
            └─────────────────────────┘

Component Design:

1. AdSlot Component

import React, { useRef, useEffect, useState } from 'react';
import { useAdManager } from './AdManager';

function AdSlot({ 
  id,
  width = 300, 
  height = 250,
  format = 'display',
  targeting = {},
  onLoad,
  onImpression,
  onClick
}) {
  const containerRef = useRef(null);
  const { registerSlot, unregisterSlot } = useAdManager();
  const [isLoaded, setIsLoaded] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const slotConfig = {
      id,
      element: containerRef.current,
      width,
      height,
      format,
      targeting,
      callbacks: {
        onLoad: () => {
          setIsLoaded(true);
          onLoad?.();
        },
        onImpression,
        onClick,
        onError: (err) => setError(err)
      }
    };

    registerSlot(slotConfig);

    return () => {
      unregisterSlot(id);
    };
  }, [id, format, width, height]);

  return (
    <div 
      ref={containerRef}
      className="ad-slot"
      data-ad-id={id}
      style={{ 
        width: `${width}px`, 
        height: `${height}px`,
        minHeight: `${height}px`, // Prevent CLS
        position: 'relative',
        backgroundColor: '#f5f5f5'
      }}
    >
      {!isLoaded && !error && (
        <div className="ad-placeholder">Loading ad...</div>
      )}
      {error && (
        <div className="ad-error">Ad failed to load</div>
      )}
    </div>
  );
}

export default AdSlot;

2. Ad Manager

class AdManager {
  constructor() {
    this.slots = new Map();
    this.visibilityManager = new VisibilityManager();
    this.rendererManager = new RendererManager();
    this.trackerManager = new TrackerManager();
    this.config = {
      lazyLoadMargin: '200px',
      viewabilityThreshold: 0.5,
      viewabilityDuration: 1000
    };
  }

  registerSlot(slotConfig) {
    const { id, element, callbacks } = slotConfig;
    
    // Store slot reference
    this.slots.set(id, { ...slotConfig, state: 'registered' });

    // Setup visibility tracking
    this.visibilityManager.observe(element, {
      onVisible: () => this.loadAd(id),
      onViewable: () => this.fireImpression(id),
      threshold: this.config.viewabilityThreshold,
      duration: this.config.viewabilityDuration
    });

    // Setup click tracking
    element.addEventListener('pointerdown', (e) => {
      this.trackerManager.trackClick(id, e);
      callbacks.onClick?.(e);
    });
  }

  async loadAd(id) {
    const slot = this.slots.get(id);
    if (!slot || slot.state !== 'registered') return;

    // Update state
    slot.state = 'loading';

    try {
      // Request ad from server
      const adData = await this.requestAd(slot);

      // Render ad
      await this.rendererManager.render(slot, adData);

      // Update state
      slot.state = 'loaded';
      slot.callbacks.onLoad?.();
    } catch (error) {
      slot.state = 'error';
      slot.callbacks.onError?.(error);
      this.trackerManager.trackError(id, error);
    }
  }

  async requestAd(slot) {
    const response = await fetch('/api/ads/request', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        slotId: slot.id,
        format: slot.format,
        dimensions: { width: slot.width, height: slot.height },
        targeting: slot.targeting,
        pageUrl: window.location.href,
        referrer: document.referrer
      })
    });

    if (!response.ok) throw new Error('Ad request failed');
    return response.json();
  }

  fireImpression(id) {
    const slot = this.slots.get(id);
    if (!slot || slot.impressionFired) return;

    slot.impressionFired = true;
    this.trackerManager.trackImpression(id, {
      viewable: true,
      format: slot.format
    });
    slot.callbacks.onImpression?.();
  }

  unregisterSlot(id) {
    const slot = this.slots.get(id);
    if (!slot) return;

    // Cleanup visibility observer
    this.visibilityManager.unobserve(slot.element);

    // Cleanup renderer
    this.rendererManager.destroy(slot.element);

    // Remove from slots
    this.slots.delete(id);
  }

  refresh(ids = []) {
    const slotsToRefresh = ids.length 
      ? ids.map(id => this.slots.get(id))
      : Array.from(this.slots.values());

    slotsToRefresh.forEach(slot => {
      if (!slot) return;
      
      // Reset state
      slot.state = 'registered';
      slot.impressionFired = false;

      // Clear current ad
      this.rendererManager.destroy(slot.element);

      // Reload ad
      this.loadAd(slot.id);
    });
  }
}

3. Visibility Manager

class VisibilityManager {
  constructor() {
    this.observers = new Map();
    this.timers = new Map();
  }

  observe(element, options) {
    const { onVisible, onViewable, threshold, duration } = options;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
          // Element is visible
          onVisible?.();

          // Start viewability timer
          const timer = setTimeout(() => {
            onViewable?.();
          }, duration);

          this.timers.set(element, timer);
        } else {
          // Element not visible, clear timer
          const timer = this.timers.get(element);
          if (timer) {
            clearTimeout(timer);
            this.timers.delete(element);
          }
        }
      },
      { 
        threshold,
        rootMargin: '200px' // Lazy load
      }
    );

    observer.observe(element);
    this.observers.set(element, observer);
  }

  unobserve(element) {
    const observer = this.observers.get(element);
    if (observer) {
      observer.disconnect();
      this.observers.delete(element);
    }

    const timer = this.timers.get(element);
    if (timer) {
      clearTimeout(timer);
      this.timers.delete(element);
    }
  }
}

4. Renderer Manager

class RendererManager {
  constructor() {
    this.renderers = new Map();
  }

  async render(slot, adData) {
    const { format } = slot;
    
    // Load appropriate renderer
    const Renderer = await this.getRenderer(format);
    
    // Create renderer instance
    const renderer = new Renderer(slot.element, adData);
    this.renderers.set(slot.element, renderer);

    // Render ad
    await renderer.render();
  }

  async getRenderer(format) {
    switch (format) {
      case 'video':
        const { VideoRenderer } = await import('./renderers/VideoRenderer');
        return VideoRenderer;
      case 'native':
        const { NativeRenderer } = await import('./renderers/NativeRenderer');
        return NativeRenderer;
      case 'carousel':
        const { CarouselRenderer } = await import('./renderers/CarouselRenderer');
        return CarouselRenderer;
      default:
        const { DisplayRenderer } = await import('./renderers/DisplayRenderer');
        return DisplayRenderer;
    }
  }

  destroy(element) {
    const renderer = this.renderers.get(element);
    if (renderer) {
      renderer.destroy();
      this.renderers.delete(element);
    }
  }
}

// Example renderer
class DisplayRenderer {
  constructor(container, adData) {
    this.container = container;
    this.adData = adData;
  }

  async render() {
    // Create ad markup
    const adElement = document.createElement('div');
    adElement.innerHTML = this.adData.creative;
    
    // Attach click handler
    adElement.addEventListener('click', () => {
      window.open(this.adData.clickUrl, '_blank');
    });

    // Insert into container
    this.container.appendChild(adElement);
  }

  destroy() {
    this.container.innerHTML = '';
  }
}

5. Tracker Manager

class TrackerManager {
  constructor() {
    this.eventQueue = [];
    this.maxBatchSize = 20;
    this.flushInterval = 5000;
    this.startAutoFlush();
  }

  trackImpression(adId, metadata) {
    this.track({
      type: 'impression',
      adId,
      timestamp: Date.now(),
      ...metadata
    });
  }

  trackClick(adId, event) {
    const clickData = {
      type: 'click',
      adId,
      coordinates: { x: event.clientX, y: event.clientY },
      timestamp: Date.now()
    };

    // Send immediately with beacon
    navigator.sendBeacon(
      '/api/ads/track',
      JSON.stringify(clickData)
    );
  }

  trackError(adId, error) {
    this.track({
      type: 'error',
      adId,
      error: error.message,
      timestamp: Date.now()
    });
  }

  track(event) {
    this.eventQueue.push(event);

    if (this.eventQueue.length >= this.maxBatchSize) {
      this.flush();
    }
  }

  async flush() {
    if (this.eventQueue.length === 0) return;

    const batch = this.eventQueue.splice(0, this.maxBatchSize);

    try {
      await fetch('/api/ads/track/batch', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(batch),
        keepalive: true
      });
    } catch (error) {
      console.error('Tracking failed:', error);
      // Store in offline queue
    }
  }

  startAutoFlush() {
    setInterval(() => this.flush(), this.flushInterval);
    window.addEventListener('beforeunload', () => this.flush());
  }
}

Error Handling:

class AdErrorHandler {
  handleError(error, slot) {
    // Log error
    console.error(`Ad error in slot ${slot.id}:`, error);

    // Track error
    this.trackError(slot.id, error);

    // Show fallback
    this.showFallback(slot);

    // Retry logic
    this.scheduleRetry(slot);
  }

  showFallback(slot) {
    slot.element.innerHTML = `
      <div class="ad-fallback">
        Advertisement could not be loaded
      </div>
    `;
  }

  scheduleRetry(slot, attempt = 1) {
    if (attempt > 3) return; // Max 3 retries

    const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
    
    setTimeout(() => {
      this.retryLoad(slot, attempt + 1);
    }, delay);
  }
}

Performance Safeguards:

class PerformanceGuard {
  constructor() {
    this.maxAdsPerPage = 10;
    this.maxConcurrentLoads = 3;
    this.currentLoads = 0;
    this.loadQueue = [];
  }

  async requestLoad(slot) {
    // Check max ads limit
    if (this.getTotalAds() >= this.maxAdsPerPage) {
      throw new Error('Max ads per page exceeded');
    }

    // Queue if too many concurrent loads
    if (this.currentLoads >= this.maxConcurrentLoads) {
      return new Promise((resolve) => {
        this.loadQueue.push(() => resolve(this.executeLoad(slot)));
      });
    }

    return this.executeLoad(slot);
  }

  async executeLoad(slot) {
    this.currentLoads++;
    
    try {
      await this.loadAd(slot);
    } finally {
      this.currentLoads--;
      
      // Process queue
      if (this.loadQueue.length > 0) {
        const next = this.loadQueue.shift();
        next();
      }
    }
  }

  checkCLS() {
    let cls = 0;
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          cls += entry.value;
        }
      }
      
      if (cls > 0.1) {
        console.warn('CLS threshold exceeded:', cls);
        // Pause ad loading
        this.pauseLoading();
      }
    }).observe({ type: 'layout-shift', buffered: true });
  }
}

12. Interview-Winning Patterns

Key Phrases to Use

Performance-Focused:

  • "Ads code must be isolated, async, observable, and measurable — because performance directly impacts revenue."
  • "We don't block the main thread with ad operations."
  • "Fire-and-forget telemetry with retry logic ensures zero data loss."
  • "Layout shifts from ads hurt both SEO and user engagement, which directly reduces revenue."

Scalability-Focused:

  • "Batching reduces network overhead by 95% compared to individual requests."
  • "Dynamic imports ensure we only load code for ad formats that are actually used."
  • "IntersectionObserver with lazy loading means ads only request resources when they're about to be viewed."

Quality-Focused:

  • "We use viewability tracking because viewable impressions command 2-3x higher CPMs."
  • "Fraud detection on the frontend catches obvious bot traffic before it reaches the billing system."
  • "Security is layered: sandboxed iframes, CSP headers, and runtime monitoring."

Design Principles to Emphasize

  1. Isolation: Ads shouldn't affect page performance
  2. Async Everything: Never block the main thread
  3. Observable: Track everything (impressions, clicks, errors, performance)
  4. Measurable: Tie metrics to business outcomes (revenue, engagement)
  5. Resilient: Handle failures gracefully with fallbacks and retries
  6. Secure: Treat third-party content as untrusted
  7. Optimized: Every kilobyte matters when running on every page

Common Follow-Up Questions

Q: How do you handle ad refresh?

"We track time-in-view and user engagement. After 30 seconds of viewability or on scroll events, we can refresh the slot. We clear the old ad, reset the impression flag, and request a new creative. This increases total impressions per page view, boosting revenue while maintaining good UX."

Q: What if the ad server is down?

"We implement exponential backoff with retries, show a fallback message after 3 failed attempts, and track the outage for monitoring. We also cache the last successful ad response as an emergency fallback, though we don't serve stale ads that could hurt advertiser trust."

Q: How do you test ad code?

"Unit tests for tracking logic, integration tests with mock ad servers, visual regression tests for CLS, and real-user monitoring (RUM) to track Core Web Vitals in production. We also do A/B testing on ad loading strategies to measure impact on engagement and revenue."

Q: How do you handle GDPR/privacy?

"We implement consent management—no tracking fires until consent is granted. We use privacy-safe identifiers, respect Do Not Track, and provide clear opt-out mechanisms. All tracking is anonymized and aggregated where possible."


Final Prep Tips

What Microsoft Ads Team Values

  1. Revenue Impact Thinking: Always connect technical decisions to business outcomes
  2. Performance Obsession: Core Web Vitals are table stakes
  3. Scale Awareness: Solutions must work at millions of requests/sec
  4. User Experience: Ads shouldn't degrade the page experience
  5. Security Mindset: Third-party content is inherently risky

Red Flags to Avoid

❌ "I'd use jQuery for this" ❌ "Just load everything synchronously" ❌ "CLS doesn't really matter" ❌ "We can track impressions on page load" ❌ "I'd use localStorage for tracking" (violates privacy) ❌ Ignoring mobile performance ❌ Not mentioning security concerns ❌ Focusing only on code, not business metrics

Strong Closing Statement

"In ad tech, every technical decision has a direct revenue impact. My approach focuses on building isolated, async systems

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment