Skip to content

Worked designs

Each one is short enough to rehearse in 10 minutes and ties to something you've done.

1. Model portfolio document generation (fact sheets with white-labeling)

Ties to: white-labeling, Model Portfolios.

  • Requirements: advisors download a fact sheet or trade commentary PDF for a model, branded with their firm. Models change daily at most; many advisors request the same model. p95 under 2 s for a cached document; new documents within a minute.
  • Estimate: 10,000 advisors × 20 downloads/day = 200k/day ≈ 2–3/s average, maybe 20/s at peak. Small.
  • API: GET /models/{id}/factsheet?format=pdf → 200 with a pre-signed S3 URL, or 202 + poll if being generated.
  • Design:
flowchart LR
  UI[React UI] --> API[Document API]
  API -->|cache key: model version + branding version| S3[(S3: generated PDFs)]
  API -->|miss| Q[Queue / Kafka: render requests]
  Q --> W[Render workers: template + data + branding to PDF]
  W --> S3
  MS[Model service] -->|model.updated event| Q
  BR[(Branding config: name, logo per firm)] --> W
  • Key decisions: cache key = model version + firm branding version, so a model update or logo change produces a new document and old links stay valid; pre-generate for popular models on model.updated events; render workers scale horizontally; validate/re-encode logos on upload.
  • Failure modes: renderer down → serve the last generated version with its date; a corrupted logo → fall back to default branding and alert.

2. A trading exchange / order matching system

Ties to: Prayog. You can go deeper than any interviewer expects here: use the scale page.

  • Single-writer matching per symbol shard, journal before answering, deterministic replay for recovery and hot standby, market data fan-out with conflation, Kafka off the order path into post-trade.

3. Real-time leaderboard

Ties to: Prayog leaderboard (Redis sorted set).

  • Requirements: 1M players, scores update on every trade/game, show top 100 and "my rank" in real time.
  • Design: score updates → Kafka (keyed by player) → consumer updates DB (source of truth) and Redis ZADD; reads: ZREVRANGE 0 99 WITHSCORES (cache 1 s), ZREVRANK player.
  • Scale: one sorted set holds 1M members easily (O(log n) ops). For much larger: shard by score range or keep exact top-K plus approximate rank buckets.
  • Failure: Redis lost → rebuild from DB and swap in atomically (RENAME a freshly built key), exactly what Prayog does.

4. Rate limiter

Ties to: Prayog's tiered token buckets.

  • Algorithms: fixed window (simple, bursts at edges), sliding window log/counter, token bucket (allows bursts up to capacity, refills at a steady rate), leaky bucket (smooth output).
  • Where: API gateway or a filter in each service; per user/API key/IP; return 429 with Retry-After.
  • Distributed: shared counters in Redis (Lua script for atomic check-and-decrement), or route each account to one gateway via consistent hashing so its bucket stays local (Prayog's scaling plan).
final class TokenBucket {
    private final long capacity; private final double refillPerNano;
    private double tokens; private long last;
    TokenBucket(long capacity, double perSecond) {
        this.capacity = capacity; this.refillPerNano = perSecond / 1e9;
        this.tokens = capacity; this.last = System.nanoTime();
    }
    synchronized boolean tryAcquire() {
        long now = System.nanoTime();
        tokens = Math.min(capacity, tokens + (now - last) * refillPerNano);
        last = now;
        if (tokens >= 1) { tokens -= 1; return true; }
        return false;
    }
}

5. Notification service (common generic question)

  • Requirements: send email/SMS/push for events (trade executed, report ready); user preferences; no duplicates; retries; 10M notifications/day.
  • Design: producers publish events to Kafka → notification service checks preferences and templates → per-channel queues → channel workers call providers (SES, SNS, FCM) with retries and backoff → status store; idempotency key per (event id, user, channel) so retries don't double-send; DLQ for permanent failures; rate limit per user.
  • Trade-offs: at-least-once delivery + dedupe; priority queues for urgent vs. marketing.

6. URL shortener (the classic warm-up)

  • Write: generate a 7-character base62 id (from a counter or random + collision check), store id → url in a key-value store. Read: GET /{id} → 301/302 redirect; heavy read traffic, so cache hot ids in Redis/CDN. 62⁷ ≈ 3.5 trillion ids. Analytics asynchronously via Kafka.

7. Report delivery to a large client base (monthly)

Ties to: Index Research client reporting.

  • Requirements: at month end, generate and deliver reports for thousands of clients reliably by a deadline.
  • Design: a scheduler creates one job per client report in a jobs table (status, attempts); workers pick jobs (SELECT ... FOR UPDATE SKIP LOCKED), generate, store, deliver (email/SFTP/portal); retries with backoff; a dashboard for operations showing pending/failed; rerun one client without rerunning all.
  • Points: idempotent delivery (don't email twice), config-driven onboarding (a new client is a row, not code), and reference data cached for the run.