0. Room method — when HLD even appears
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
- Do not open this chapter unprompted on a two-DSA loop. Rank A UTA reports are mostly two DSA + LP. Draw boxes only after they say scale, distributed, “design the system,” or they name a product.
- Arijit Char AUTA Tech 2 (LinkedIn) had an unnamed system design after unnamed LP. Prompt stays unnamed. Do not fill it with TinyURL, Kafka, or CampusToCareer “APIs, databases, caching layers” (Class C invention, not an IE).
- ~40 min if they actually asked HLD: 0–5 restated scope + clarify; 5–12 four-to-six boxes and APIs they named; 12–25 one deep slice (hottest read or write + the store table + the Java core); 25–35 10× bottleneck if they asked scale; 35–40 one failure + consistency honesty. Stop adding boxes.
- Clarify every time: QPS or “single box is fine”; payload size; consistency (stale read OK? unique write?); single-region first; reject vs queue vs drop; in-memory vs durable. Do not invent a QPS they did not give.
- If they say “classes / write complete code including main”: that is chapter 22 / chapter 06, not this Kafka-shaped chapter.
I treat full retail HLD as not the SDE I UTA default. Independent Rate Limiter live count is one, from an OA-plus-three system-design loop, not UTA two-DSA, and SDE-2 videos do not add a second count. If this loop is two DSA I stay on Java. If they ask me to design a system after two lives I answer: I pin QPS, payload, consistency, and single-region first, I draw four to six boxes, I name the store that owns the invariant, and I can Live-Code the in-process core. I will not volunteer a forty-minute Kafka essay, and I will not stamp a pasted “common HLD topics” list as Job 10454435 evidence.
Decision box — LLD vs HLD (when to stay in chapter 22)
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
| They say | You are in | Open | Do not |
“Classes,” “objects,” “write complete code including main,” “in-memory” | LLD — types, HashMap, one lock, 3 APIs | 22, 06 | Draw ALB, Kafka, multi-region S3 |
| TUF 203 notification request (complete code + main) | LLD coded request | Chapter 22 notification request | SNS / SES / Pinpoint novel |
| Bhavya LRU Cache (Hyd Interview 2) | Live Code HashMap + DLL | 05 LRU, then this chapter’s cache card only if they say “now Redis” | Call LRU “I implemented Redis”; call it Login Tracker |
| Locker, parking, logger, dog check-in, searchable collection | LLD / OOD | 06 / 22 | Warehouse Kafka |
| “Scale this,” “distributed,” “N app servers,” “design TinyURL / S3 / Uber” | HLD — boxes + stores | This chapter | Skip the Java core if they then say “implement it” |
| IE.in 2025-grad Rate Limiter + distributed | LLD first, then light HLD | 06 Rate Limiter then this card | Treat SDE-2 YouTube as count 2; treat LC 8362604 as a second LLD |
| Arijit unnamed AUTA SD | Clarify list, not a named product | This room method | Invent TinyURL / Rate Limiter / SNS as “what Arijit was asked” |
| Nitesh “notification system–like” | LLD-shaped, prompt vague | Chapter 22 + this card only if they push fan-out / retry | Invent AWS Pinpoint |
Low-level design is classes and in-memory invariants I can type in Live Code. High-level design is boxes and stores and who calls whom. If they want complete code including main, I stay in chapter 22. If they want N servers and a shared store, I open this chapter, still single-region first, and I can drop into the Java core when they say implement. I do not mix a pasted common-HLD list into Job 10454435.
Three probes. “Is this UTA-default HLD?” No — UTA Rank A is mostly two-DSA; this book answers HLD if they ask. “Did you ship Kafka at Ylogx?” No. “Where is the Rate Limiter LLD?” Chapter 06; count stays 1.
1. URL Shortener — unique short codes, redirect, collisions, optional custom alias Standard CS
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Design a URL shortener: encode a unique short code, redirect to the long URL, handle collisions, optional custom alias. No first-hand SDE I UTA URL names this as a live prompt in Question-Research-BIBLE.md §5.C. TUF questions-asked.md explicitly notes this dump did not name TinyURL. This card is Class C / Standard CS, not Job 10454435. Resume analogue only if they pivot: GiftedBooks stores user PDFs for RAG (not a short-link product); Ylogx exposes REST under CloudFront (not bit.ly).
Clarify
- QPS: create vs redirect ratio? Single box OK to start?
- Payload: max long-URL length; custom alias allowed? reserved words?
- Consistency: same long URL → same code, or new code every time? Redirect 302 vs 301?
- Single-region first. Analytics / expiry only if they ask.
- Unguessable codes vs sequential Base62 (enumerable).
What we design is a create API that returns a unique short code and a GET that 302s to the long URL. I start single-region: app plus one store that owns short → long. I pick a Base62 counter if they want uniqueness by construction and they accept that sequential codes are enumerable. I pick a hash of the long URL if they want the same URL to collapse to one code, and I handle collisions with a salt retry. I do not open Kafka or multi-region S3 for a shortener. A Bloom filter is Standard CS I can mention as “probably not in the DB” — I did not ship one. Why not only an in-memory HashMap? Process restart loses every link. Why not put the long URL in the path? That is not shortening. Why not CloudFront as the database? CloudFront on my resume is a CDN in front of Ylogx ECS, not a key-value store I built.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
HashMap<code, longUrl> (Live Code) or SQL/KV unique on code | Canonical redirect | Not a list scan; not S3 as the lookup (S3 is blobs, not low-latency GET by 7-char key unless they force it) | GET code → long URL; unique constraint = no two owners of one code | O(1) average get/put; DB unique index on code |
Optional HashMap<longUrl, code> | Idempotent shorten | Without it, the same URL mints many codes (OK if they want that) | Repeat POST returns existing code | O(1); unique on long_url if required |
| Base62 counter (atomic) | Monotonic id → code | Vs hash: no collision, but enumerable / guessable | encode(id++) then insert; DB sequence or Redis INCR | O(k) for k digits; INCR is O(1) |
| Hash of URL + salt | Code derived from content | Vs counter: same URL same code; collision possible | Take bits, Base62, on collision salt++ | O(1) hash; expected few retries |
| Bloom filter (optional, Standard CS, not shipped) | Probabilistic “seen” | Cannot prove membership; false positives only | Skip some DB hits on “this code is free?” — still confirm in DB | O(k hashes); I did not ship this |
Components (boxes) and who calls whom
Browser → App (create / redirect)
→ Store: unique(code → longUrl)
Optional: cache hot redirects in Redis (client, not “I built Redis”)
No Kafka. Single-region. CloudFront may cache 302s if they ask CDN — that is edge HTTP, not Ylogx-as-TinyURL.
Browser calls App. App reads/writes Store. Cache is a later hop for hot GET, never the uniqueness owner. Uniqueness lives in the store’s unique index or the in-process map for Live Code.
APIs
POST /v1/shorten
body { "longUrl": "...", "customAlias": "optional" }
201 { "code": "aZ3", "shortUrl": "https://host/aZ3" }
409 custom alias taken
GET /{code}
302 Location: <longUrl>
404 unknown code
GET /v1/expand/{code} // optional JSON, no redirect
End-to-end sequence
Happy — create.
- Client POST long URL (optional alias).
- App validates URL; rejects javascript: and empty.
- If alias: try insert; unique violation → 409.
- Else mint Base62(id) or hash; insert code → long.
- Collision on generated code: retry with next id or salt (do not overwrite).
- 201 with code.
Happy — redirect. GET code → lookup → 302. Unknown → 404. Do not 200 with a phishing page.
Failure. Duplicate alias: 409, existing mapping unchanged. Store down: 503, no silent drop of creates. Two concurrent creates of the same generated code: one insert wins, loser retries — uniqueness is the store’s job, not a check-then-act on two ECS tasks.
In-process Java core
If they say “implement encode / uniqueness,” this is the Live Code. Not empty.
import java.util.*;
final class UrlShortener {
private static final String ALPH =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final Map<String, String> shortToLong = new HashMap<>();
private final Map<String, String> longToShort = new HashMap<>();
private long nextId = 1;
synchronized String shorten(String longUrl, String customAlias) {
if (longUrl == null || longUrl.isBlank()) {
throw new IllegalArgumentException("blank url");
}
if (customAlias != null) {
if (shortToLong.containsKey(customAlias)) {
throw new IllegalStateException("alias taken");
}
bind(customAlias, longUrl);
return customAlias;
}
String existing = longToShort.get(longUrl);
if (existing != null) {
return existing;
}
for (int attempt = 0; attempt < 8; attempt++) {
String code = base62(nextId++);
if (!shortToLong.containsKey(code)) {
bind(code, longUrl);
return code;
}
}
throw new IllegalStateException("could not mint unique code");
}
synchronized String resolve(String code) {
String url = shortToLong.get(code);
if (url == null) {
throw new NoSuchElementException(code);
}
return url;
}
/** Hash-shaped alternative: same URL tends to same code; collide → salt. */
static String hashCodeCandidate(String longUrl, int salt) {
int h = (longUrl + "#" + salt).hashCode();
if (h < 0) {
h = -h;
}
return base62(h);
}
private void bind(String code, String longUrl) {
shortToLong.put(code, longUrl);
longToShort.putIfAbsent(longUrl, code);
}
static String base62(long n) {
if (n == 0) {
return "0";
}
StringBuilder sb = new StringBuilder();
long x = n;
while (x > 0) {
sb.append(ALPH.charAt((int) (x % 62)));
x /= 62;
}
return sb.reverse().toString();
}
}
Scale 10× honest
More create QPS: the uniqueness owner is still one sequence or one unique index — I did not shard. I would add a cache of hot redirects (Ylogx taught me Redis as a client: −35% bot DB latency, not a shortener I shipped). I would not put tenant BI JSON in CloudFront; a public 302 to a marketing URL is cacheable, a private dashboard is not. Ylogx 99.9% and sub-210 ms are serving metrics on ECS behind ALB/CloudFront, not bit.ly SLAs. I did not ship S3 or a ride marketplace. Multi-region active-active shortener is a later CS sketch (id ranges per region), not a resume claim.
Consistency / uniqueness / drop-vs-block / exactly-once
Uniqueness of codes is a compare-and-insert (unique index), not check-then-act across two tasks. Same long URL collapsing to one code is an application choice plus unique on long_url. Redirects are reads; stale cache of a 302 is usually OK; overwriting a live code is not. Creates block (409/503) rather than drop — dropping a shorten request loses the user’s link. Exactly-once minting is “at most one row per code”; I do not claim exactly-once HTTP in a distributed log I did not ship.
1 — What did you do (resume)? I did not ship a shortener. Closest: GiftedBooks maps a user to their PDF for RAG (isolation, not a global short code). 2 — Differently? If they need unguessable codes I would not use a raw sequential Base62; I would still unique-index the code. 3 — How do you know the metric? There is no shortener metric on the resume. I will not invent click-through %. Ylogx 99.9% / sub-210 ms stay on the BI path.
Do not say. “I built TinyURL at Ylogx.” Bloom filter as if shipped. Kafka for redirects. Multi-region S3 as the default. Phishing pages on unknown codes. Job 10454435 will ask this.
2. Rate Limiter — token bucket and sliding window; distributed Redis IE-asked
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Design a Rate Limiter and scale it across distributed application servers. First-hand: IE.in 2025-grad, Second Technical 13 Feb 2025, OA+3 system design, not UTA two-DSA. Independent SDE I live count = 1. TUF 08 describes the same family (OA numbered as their R1; their R3 = Rate Limiter; sliding window vs token bucket; Redis / clock / sharding talk) — that does not become count 2. SDE-2 Super Day / YouTube d0yM6h0XRxk / Hitesh L5 do not add count 2. LC 8362604 “rate limiting decisions” / geofencing were LP follow-ups, not a second LLD. Full five-strategy Java: chapter 06. This card is the HLD shape plus the two algorithms they named (token bucket and sliding window) plus the Java core. Not Job 10454435.
Clarify
- Key: user, IP, API key, route, AND of several?
- N requests in W seconds; burst allowed?
- Reject = HTTP 429 + Retry-After vs queue vs silent drop?
- One JVM vs N tasks behind a load balancer?
- Exact vs approximate; Redis down: fail-closed vs fail-open by endpoint?
- Single-region first. QPS: they give it, or “single box is fine.”
What we design is middleware in front of business logic: allow(key) returns allowed or 429 with Retry-After. I do not copy the check into every use case. On one process I keep a HashMap from key to bucket or window, under one lock. I write token bucket when they want bursts: capacity tokens, refill over time, refuse when empty. I write sliding window when they want “at most N in the last W seconds”: a deque of accepted timestamps, drop those older than W, admit if size is under N. Why not fixed window alone? It can admit almost 2× at the boundary. Why not sliding log in Redis for every hot key? Memory is O(requests in the window). Across ECS tasks a JVM map is wrong because ALB fans out and each task would grant a full budget — the same math moves into Redis as one atomic update. I describe Lua as “one script: read, refill, decide, write” so two servers cannot spend the same token; I am not giving an exploit. Ylogx Redis cut bot database latency 35 percent as a cache. I did not ship this limiter. If they only wanted classes, I stay in chapter 06 and I do not start a Kafka essay.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
HashMap<key, Bucket> tokens + lastRefill | Token-bucket state per key | Vs sliding log: O(1) memory, allows burst to capacity | Refill by elapsed time, consume 1 if tokens ≥ 1 | O(1) under one lock; space O(keys) |
HashMap<key, Deque<Long>> | Accepted timestamps in window W | Vs token bucket: exact N in W; vs fixed window: no 2× seam burst | Evict head < now−W; admit if size < N | Amortized O(1); space O(N) per hot key |
| Sliding counter (talk, optional code in 06) | Current + previous bucket counts | O(1) approx (Cloudflare/Kong-style) | Weighted previous + current vs N | O(1) memory |
| Redis hash + atomic script | Shared tokens/window for N app servers | Vs JVM map: ALB would N× the budget | TIME, refill, decrement, EXPIRE idle keys in one atomic script | One RTT; O(1) state for token bucket |
| Leaky / fixed (name only) | — | Fixed = seam burst; leaky = smooth, may queue | Say, do not code all five unless they ask | See 06 |
Components (boxes) and who calls whom
Client → CloudFront (optional edge) → ALB → ECS task
→ RateLimitFilter (this)
→ Redis (distributed state)
→ business service only if allowed
Reject path: 429 + Retry-After. No Kafka required for a limiter.
Client hits the load balancer; each ECS task runs the same filter; the filter calls Redis (or the local map on one box); on allow, the task runs the API. Ylogx path CloudFront → ALB → ECS is where a gateway limiter could sit — analogy, not a shipped Ylogx limiter.
APIs
boolean / Decision allow(String key, long nowNanos)
Decision { allowed, retryAfterMillis }
HTTP:
200 (or pass through) when allowed
429 Too Many Requests
Retry-After: <seconds or HTTP-date>
Do not 200 with empty body. Do not 503 unless *this* service is sick.
403 is authZ (RLS/RBAC), not quota.
End-to-end sequence
Happy. Request arrives with user id. Filter builds key user:42:/checkout. Token bucket refills, tokens ≥ 1, decrement, proceed to handler. Sliding window: drop timestamps older than W, size < N, append now, proceed.
Failure — over budget. Return 429 and Retry-After from remaining refill time or time until the oldest timestamp exits the window. Do not enqueue checkout unless they asked a queue. Do not silent-drop (honest clients retry harder).
Failure — two tasks, no Redis. Each JVM admits a full budget → 2× N. That is why distributed state is required behind ALB.
Failure — Redis down. Fail-closed on payments (false 429s, origin lives). Fail-open on a cheap pixel (availability, origin can melt). Pick from the endpoint. Ylogx analogue: Redis down on the bot cache fail-opens to Postgres; RLS never fail-opens. That cache is not this limiter.
In-process Java core
Token bucket and sliding window. Full five strategies and the filter: chapter 06.
import java.util.*;
interface RateLimiter {
Decision allow(String key, long nowNanos);
}
final class Decision {
final boolean allowed;
final long retryAfterMillis;
private Decision(boolean allowed, long retryAfterMillis) {
this.allowed = allowed;
this.retryAfterMillis = retryAfterMillis;
}
static Decision yes() { return new Decision(true, 0); }
static Decision no(long ms) { return new Decision(false, Math.max(1, ms)); }
}
/** Token bucket: HashMap key → tokens + last refill. Burst up to capacity. */
final class TokenBucketLimiter implements RateLimiter {
private static final class Bucket {
double tokens;
long lastRefill;
Bucket(double tokens, long lastRefill) {
this.tokens = tokens;
this.lastRefill = lastRefill;
}
}
private final int capacity;
private final double refillPerSecond;
private final Map<String, Bucket> buckets = new HashMap<>();
TokenBucketLimiter(int capacity, double refillPerSecond) {
if (capacity <= 0 || refillPerSecond <= 0) {
throw new IllegalArgumentException("configuration");
}
this.capacity = capacity;
this.refillPerSecond = refillPerSecond;
}
public synchronized Decision allow(String key, long now) {
Bucket b = buckets.computeIfAbsent(key, k -> new Bucket(capacity, now));
if (now < b.lastRefill) {
throw new IllegalArgumentException("clock reversed");
}
double elapsed = (now - b.lastRefill) / 1_000_000_000.0;
b.tokens = Math.min(capacity, b.tokens + elapsed * refillPerSecond);
b.lastRefill = now;
if (b.tokens >= 1.0) {
b.tokens -= 1.0;
return Decision.yes();
}
double seconds = (1.0 - b.tokens) / refillPerSecond;
return Decision.no((long) Math.ceil(seconds * 1000.0));
}
}
/** Sliding window (log): HashMap key → deque of accepted timestamps. Exact N in W. */
final class SlidingWindowLimiter implements RateLimiter {
private final int maximum;
private final long windowNanos;
private final Map<String, Deque<Long>> logs = new HashMap<>();
SlidingWindowLimiter(int maximum, long windowNanos) {
this.maximum = maximum;
this.windowNanos = windowNanos;
}
public synchronized Decision allow(String key, long now) {
Deque<Long> log = logs.computeIfAbsent(key, k -> new ArrayDeque<>());
while (!log.isEmpty() && now - log.peekFirst() >= windowNanos) {
log.removeFirst();
}
if (log.size() < maximum) {
log.addLast(now);
return Decision.yes();
}
long retry = (windowNanos - (now - log.peekFirst())) / 1_000_000L;
return Decision.no(retry);
}
}
final class RateLimitHttp {
static String rejectHeaders(Decision d) {
// HTTP 429 Too Many Requests; Retry-After in seconds
long seconds = Math.max(1, (d.retryAfterMillis + 999) / 1000);
return "HTTP/1.1 429 Too Many Requests\nRetry-After: " + seconds;
}
}
/*
* Distributed (concept, not an exploit):
* KEYS[1] = limiter key
* 1. now = Redis TIME inside the script (not each JVM wall clock)
* 2. read tokens, last_refill (or window members)
* 3. refill / evict expired
* 4. if allowed then decrement / ZADD else deny
* 5. EXPIRE idle keys; return {allowed, retry_ms}
* One script is atomic: GET-refill-DECR-SET cannot race across ECS tasks.
*/
Scale 10× honest
Hot key is inherently contended. I did not shard a global limiter. Approximate local quotas plus periodic reconcile is a CS trade (correctness vs availability) — I did not ship it. Ylogx: Redis −35% bot DB, 99.9% uptime, sub-210 ms, CloudFront/ECS/ALB — still not a shipped limiter, still not Kafka. Multi-region strict global N is a consensus/latency conversation I will not claim I ran. Login Tracker is unverified and is not this prompt.
Consistency / uniqueness / drop-vs-block / exactly-once
Token bucket is approximate in time (continuous refill). Sliding log is exact for that key on one store. Distributed Lua makes the decision atomic, not “exactly-once HTTP.” Reject is block with 429, not silent drop, unless they asked drop. Fail-closed vs fail-open is endpoint risk, not a waffle. Two policies AND together (user + global). I do not claim exactly-once delivery of limited events.
1 — What did you do? I did not ship a limiter. Ylogx Redis is cache-aside on the bot path (−35%). If they switch to LP “rate limiting decisions” (LC 8362604), I tell fail-closed RLS vs fail-open cache — I do not start coding unless they switch to LLD. 2 — Differently? I would put the limiter in middleware from day one so use-cases cannot forget it. 3 — How do you know the metric? −35% is bot DB latency, not limiter throughput. I will not invent denied-QPS.
Do not say. “I built this at Ylogx.” Count = 2 from YouTube. LC 8362604 is a second LLD. Elevator DoS kiosk is a second limiter IE. Silent 200. 503 for quota. Kafka as the limiter. Job 10454435 will ask this. UTA-default HLD.
3. Notification / messaging — channels, template, fan-out, retry, idempotency IE-asked Standard CS
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Two different first-hand families, neither Job 10454435. (1) TUF 203: write complete code including main for a notification request system — that is LLD, chapter 22, OA-as-R1 trap (their R3). (2) Nitesh Khanna ~Mar 2026: LLD similar to a notification system — prompt vague; do not invent SNS/SES/Pinpoint. This card is the HLD slice if they push channels, templates, fan-out, retry, and idempotency. Resume analogue only if they pivot: Argus automated alerts after Postgres event rows (20+ cameras), not a broker I named.
Clarify
- In-process Observer vs email / SMS / push?
- Sync on the request thread vs async worker?
- QPS, payload size, template vs raw body?
- Opt-out / preferences? Fan-out to many users or many channels?
- At-least-once vs at-most-once? Single-region first.
- If they want
main and classes → chapter 22 immediately.
What we design, if they stay on HLD, is accept a notification request, render a template, pick channels the user allows, and deliver with retry. The request object is the LLD they asked Nitesh / TUF 203 to code; the system around it is a store for templates and preferences, a work queue only if they asked async, and workers per channel. Why this shape: the HTTP handler should not block on SMTP. Why not a Kafka novel: I did not operate Kafka, SQS, or SNS. A BlockingQueue plus workers is enough until they push scale, then I say enqueue, workers, idempotency key, backoff, dead-letter, stop. Why not exactly-once? I did not ship a transactional outbox. Delivery is at-least-once plus an idempotent consumer keyed on event_id. Argus is the resume analogue: detection becomes a Postgres event, then an alert — I log events, not 20×24 frames per second as rows, and I do not invent a topic partition.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
EnumMap<Channel, Sender> | Email / SMS / push implementations | Vs if/else pile; vs one god service | Fan-out loops allowed channels | O(channels) ≤ 3 |
| Template store (map or table) | templateId → body with placeholders | Vs hard-coded strings in senders | Render vars before send | O(1) lookup |
| Preference store | userId → allowed channels / opt-out | Vs always-spam | allows(user, channel) gate | O(1) per user |
HashSet<idempotencyKey> or unique table | Seen request / event ids | Vs “trust the broker exactly-once” | Duplicate POST or retry does not double-send | O(1); unique index |
Optional BlockingQueue / broker (CS) | Pending deliveries | I did not ship Kafka; name pub/sub without a cluster autobiography | Decouple accept from send; retry / DLQ | Offer O(1); workers pull |
Components (boxes) and who calls whom
Client → Notification API (validate, idempotency, 202)
→ Template + Preference stores
→ [optional] queue
→ Channel workers: EmailSender / SmsSender / PushSender
Failure: retry with backoff → DLQ after N; never silent success
Argus analogue: detector → Postgres event → alert path (no named broker)
Client calls the API. API writes the intent (idempotent). Workers call providers. Providers do not call the API back except webhooks if they ask. Rate-limit the kiosk/user here is elevator-style per-user cap, not a second Rate Limiter IE (count stays 1).
APIs
POST /v1/notifications
Idempotency-Key: evt-99
{ "userId", "templateId", "channels": ["SMS","EMAIL"], "vars": { } }
202 { "notificationId" } // accepted
200 duplicate key, same notificationId
POST /v1/preferences/{userId} { "sms": false }
Internal: Sender.send(userId, body) throws on provider failure
End-to-end sequence
Happy. POST with idempotency key. Insert seen-key (unique). Render template. For each channel prefs allow, enqueue or send. 202. Worker delivers SMS. Duplicate POST with same key returns the same id and does not send again.
Failure — provider 5xx. Retry with backoff; after N, DLQ; request stays “accepted” not “delivered.” Do not tell the client 200 delivered if the worker never succeeded.
Failure — crash after send, before persisting seen. At-least-once: a retry may send twice unless the provider also sees the idempotency key. I say that out loud. I do not claim exactly-once.
In-process Java core
This is the request/idempotency/fan-out you would Live-Code. Full main notification request: chapter 22.
import java.util.*;
enum Channel { EMAIL, SMS, PUSH }
interface Sender {
void send(String userId, String body) throws Exception;
}
final class NotifyRequest {
final String idempotencyKey;
final String userId;
final String templateId;
final Set<Channel> channels;
final Map<String, String> vars;
NotifyRequest(String idempotencyKey, String userId, String templateId,
Set<Channel> channels, Map<String, String> vars) {
this.idempotencyKey = idempotencyKey;
this.userId = userId;
this.templateId = templateId;
this.channels = channels;
this.vars = vars;
}
}
final class NotificationService {
private final Set<String> seen = new HashSet<>();
private final Map<String, String> templates = new HashMap<>();
private final Map<String, Set<Channel>> prefs = new HashMap<>();
private final Map<Channel, Sender> senders;
NotificationService(Map<Channel, Sender> senders) {
this.senders = senders;
templates.put("ppe", "PPE alert: {detail}");
}
/** @return false if duplicate key (already accepted) */
synchronized boolean submit(NotifyRequest r) {
if (r.idempotencyKey == null || r.idempotencyKey.isBlank()) {
throw new IllegalArgumentException("idempotency key required");
}
if (!seen.add(r.idempotencyKey)) {
return false;
}
String body = render(r.templateId, r.vars);
Set<Channel> allowed = prefs.getOrDefault(r.userId, r.channels);
for (Channel ch : r.channels) {
if (!allowed.contains(ch)) {
continue;
}
Sender sender = senders.get(ch);
if (sender == null) {
continue;
}
try {
sender.send(r.userId, body);
} catch (Exception ex) {
// at-least-once: keep seen so a naive retry of submit() does not
// double-fanout; a worker retry uses the same key at the provider.
throw new IllegalStateException("send failed; retry worker", ex);
}
}
return true;
}
private String render(String templateId, Map<String, String> vars) {
String t = templates.getOrDefault(templateId, "{detail}");
String detail = vars.getOrDefault("detail", "");
return t.replace("{detail}", detail);
}
}
final class NotificationMain {
public static void main(String[] args) {
Map<Channel, Sender> senders = new EnumMap<>(Channel.class);
senders.put(Channel.SMS, (user, body) -> { /* provider */ });
senders.put(Channel.EMAIL, (user, body) -> { /* provider */ });
NotificationService svc = new NotificationService(senders);
NotifyRequest r = new NotifyRequest(
"evt-99", "u1", "ppe",
EnumSet.of(Channel.SMS),
Map.of("detail", "no helmet camera 7"));
svc.submit(r);
svc.submit(r); // duplicate key, no second SMS
}
}
Scale 10× honest
More users: persist seen-keys and templates; workers per channel; I did not shard. I will mention pub/sub as Standard CS without inventing a Kafka cluster on the resume. Ylogx dashboards are synchronous REST (sub-210 ms, no LLM on tiles), Redis is a cache not a notification bus. Argus: 20+ cameras, Postgres logs, −50% violations, 2× compliance — alerts after events, not Prometheus and not SNS. Per-user cap if they say DoS — not Rate Limiter count 2.
Consistency / uniqueness / drop-vs-block / exactly-once
At-least-once + idempotent consumer. I do not claim exactly-once I did not ship. Uniqueness is the idempotency key (HashSet / unique table). Accept path blocks the client with 202/409, it does not drop the request on the floor. Provider overload: retry then DLQ (drop from hot path, keep in DLQ) — say drop-vs-block out loud. Preferences make fan-out a filtered loop, not “email the world.”
1 — What did you do? TUF 203 wanted request LLD — I would code chapter 22. Intern: Argus event then alert, Postgres as the fact, no named broker. 2 — Differently? Persist the outbox row before calling SMS so a crash retries safely. 3 — How do you know the metric? Argus −50% violations / 2× compliance are site outcomes after alerts, not “messages/sec.” I will not invent SES delivery %.
Do not say. “I ran Kafka/SNS at Ylogx.” Exactly-once. Pinpoint. This is Rate Limiter count 2. Job 10454435 named this. Invent Nitesh’s exact prompt. Phishing emails as a “channel demo.”
4. File storage S3-like — object, key, bucket, multipart, metadata vs blob Standard CS Resume-derived
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Design object storage: bucket, key, object bytes, multipart upload, metadata. No first-hand SDE I UTA “build S3” live prompt in §5.C. IE.in L4 listed S3 among after-BR trivia words — one breath, not a product design. This card is Class C / Standard CS. Resume: CloudFront is a CDN in front of Ylogx (99.9%, sub-210 ms, ALB/ECS), not “I built S3.” GiftedBooks RAG over user PDFs (sub-300 ms, 99.5%) is retrieve-then-generate on their upload, not an object-store product. Argus: Postgres event rows; bulk video is a different storage class — I do not invent an Argus bucket name. InstaRecon: security-awareness demo with consent; no production attacks; if they ask about scanning files, that one-liner then redirect to Ylogx isolation / GiftedBooks per-user PDF.
Clarify
- QPS reads vs writes? Max object size? Multipart threshold?
- Strong read-after-write on the same key? ACL now or follow-up?
- Single-region first. Multi-region S3 is not the SDE I default essay.
- Metadata listing vs byte GET (different stores).
What we design is split stores on purpose: a metadata database holds bucket, key, size, content-type, etag, blob id, and optional ACL; a blob store holds the bytes keyed by blob id. Why this split: listing “objects in a prefix” is small structured rows; the PDF or video is large sequential IO. Putting megabytes in Postgres inflates backups and makes “which objects in bucket B” a blob scan. Putting all metadata only in filenames loses query. Multipart: client uploads parts, server concatenates or stores an ordered part list, then a complete() writes one metadata row pointing at the assembled blob. Why not “CloudFront is S3”? CloudFront on my resume caches static JS at the edge in front of ALB and ECS; origin objects for a BI app are not a public object API I implemented. I start single-region. ACL is a follow-up table, not the first box.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
Metadata DB (rows) or HashMap<bucket/key, Meta> | size, etag, contentType, blobId, state | Vs bytes in SQL; vs only a filesystem tree | List, HEAD, ACL join, multipart state | Lookup O(1) / index; list by prefix = range scan |
Blob store Map<blobId, byte[]> or disk/object | Opaque bytes | Vs metadata in the same row as 2 GB | GET object streams bytes; PUT writes blob then meta | Sequential IO; keyed O(1) |
Map<uploadId, List<part>> | Multipart parts in order | Vs one-shot PUT for huge files | Complete concatenates; abort deletes parts | O(parts) assemble |
| Optional ACL table (follow-up) | principal → allow/deny on bucket/key | Not the first invariant | Authorize before GET/PUT | O(1) if indexed |
| CDN (CloudFront on resume) | Cacheable public GET | Not the metadata SoT; not tenant JSON | Edge cache of immutable objects | HTTP cache; Ylogx used it for static, not RLS rows |
Components (boxes) and who calls whom
Client → API (auth, ACL follow-up)
→ Metadata DB (bucket/key → blobId)
→ Blob store (blobId → bytes)
Multipart: Initiate → UploadPart → Complete → single Meta row
CDN optional in front of GET for public immutable objects
Ylogx actual: Browser → CloudFront → ALB → ECS (not this product)
GiftedBooks: upload PDF → chunk/index → RAG (not S3 API)
Client talks to API. API writes blob first, then metadata (so a crash does not advertise a key with no bytes). GET reads metadata then blob. CDN is a later cache of GET, never the uniqueness owner of keys.
APIs
PUT /b/{bucket}/{key} // small object, body = bytes
HEAD /b/{bucket}/{key} // metadata only
GET /b/{bucket}/{key} // bytes
DELETE /b/{bucket}/{key}
POST /b/{bucket}/uploads → { uploadId }
PUT /b/{bucket}/{key}?uploadId=&partNumber=
POST /b/{bucket}/{key}/complete { uploadId, parts: [etag] }
POST /b/{bucket}/{key}/abort
ACL follow-up: PUT /b/{bucket}/{key}/acl
End-to-end sequence
Happy — small PUT. Auth. Write bytes to blob store → blobId. Insert metadata row (bucket, key, blobId, size, etag). 200. GET: load meta, stream blob.
Happy — multipart. Initiate uploadId. N part PUTs append to part list. Complete: assemble blob, write meta, delete scratch parts. Concurrent complete: one winner via unique (bucket, key) version or If-Match.
Failure. Blob written, metadata insert fails: orphan blob (GC later) — better than a key that 200s with empty body. Metadata first then blob: GET 200 missing bytes — worse. Unknown key: 404, not a phishing file. Store down: 503, do not drop a PUT on the floor and say 200.
In-process Java core
import java.util.*;
import java.security.MessageDigest;
final class ObjectMeta {
final String bucket, key, blobId, contentType;
final long size;
final String etag;
ObjectMeta(String bucket, String key, String blobId,
String contentType, long size, String etag) {
this.bucket = bucket;
this.key = key;
this.blobId = blobId;
this.contentType = contentType;
this.size = size;
this.etag = etag;
}
}
final class ObjectStore {
private final Map<String, ObjectMeta> meta = new HashMap<>();
private final Map<String, byte[]> blobs = new HashMap<>();
private final Map<String, NavigableMap<Integer, byte[]>> parts = new HashMap<>();
private long blobSeq;
private static String nk(String bucket, String key) {
return bucket + "/" + key;
}
synchronized ObjectMeta put(String bucket, String key, byte[] body, String contentType) {
String blobId = "b" + (++blobSeq);
blobs.put(blobId, body);
String etag = etag(body);
ObjectMeta row = new ObjectMeta(bucket, key, blobId, contentType, body.length, etag);
meta.put(nk(bucket, key), row);
return row;
}
synchronized byte[] get(String bucket, String key) {
ObjectMeta row = meta.get(nk(bucket, key));
if (row == null) {
throw new NoSuchElementException(key);
}
return blobs.get(row.blobId);
}
synchronized String initiateMultipart() {
String uploadId = "u" + UUID.randomUUID();
parts.put(uploadId, new TreeMap<>());
return uploadId;
}
synchronized void uploadPart(String uploadId, int partNumber, byte[] body) {
NavigableMap<Integer, byte[]> p = parts.get(uploadId);
if (p == null) {
throw new NoSuchElementException(uploadId);
}
p.put(partNumber, body);
}
synchronized ObjectMeta complete(String bucket, String key, String uploadId, String contentType) {
NavigableMap<Integer, byte[]> p = parts.remove(uploadId);
if (p == null || p.isEmpty()) {
throw new IllegalStateException("no parts");
}
int total = 0;
for (byte[] b : p.values()) {
total += b.length;
}
byte[] assembled = new byte[total];
int off = 0;
for (byte[] b : p.values()) {
System.arraycopy(b, 0, assembled, off, b.length);
off += b.length;
}
return put(bucket, key, assembled, contentType);
}
static String etag(byte[] body) {
try {
byte[] d = MessageDigest.getInstance("SHA-256").digest(body);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
sb.append(String.format("%02x", d[i]));
}
return sb.toString();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
Scale 10× honest
I did not ship S3. 10× objects: index (bucket, key); keep blobs off the metadata DB; GC orphans. Ylogx CloudFront/ECS/ALB 99.9% and sub-210 ms are the BI serving path — static bundles at the edge, not a multi-region object fabric. GiftedBooks sub-300 ms is RAG generate-small, not GET 2 GB. Argus stores events in Postgres, not video in the table. I did not shard. Multi-region replication is a later CS sentence, not the UTA default essay.
Consistency / uniqueness / drop-vs-block / exactly-once
Key uniqueness is (bucket, key) in metadata — last complete() wins unless they asked versioning. Read-after-write: write blob then meta so GET does not 200 empty. Multipart complete is not exactly-once if retried without a fencing token — unique complete per uploadId. PUT failures block with 5xx, do not drop bytes and claim stored. ACL deny is 403, not 404 theatre unless they ask hiding existence.
1 — What did you do? CloudFront in front of Ylogx, not S3. GiftedBooks: per-user PDF for RAG. Argus: events in Postgres, media conceptually off-row. InstaRecon: consent demo, no attacks. 2 — Differently? I would version keys if they need undelete. 3 — How do you know the metric? 99.9% / sub-210 ms are Ylogx serving, not object durability nines I measured.
Do not say. “I built S3.” “EKS object cluster.” Multi-region S3 novel as UTA-default. Kafka for PUT. Qdrant as object store. InstaRecon exploit steps. Phishing file hosting. Job 10454435 will ask this.
5. Ride booking / order management — matching, inventory, state machine, geo grid Standard CS
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Design ride booking or order management: match supply to demand, inventory of drivers or SKUs, a state machine, nearby search. No first-hand SDE I UTA Uber/order HLD in §5.C. Class C / Standard CS. StratifyLabs “marketplace 50+ models” is an ML model catalog, not rides. Ylogx is BI reports and 30 KPI dashboards, not a ride marketplace. Horizon costmap is rover occupancy, not driver dispatch. Do not invent Kafka on the resume; pub/sub is a Standard CS mention for live location.
Clarify
- Rides vs e-commerce orders (inventory units vs drivers)?
- QPS: request vs location pings? Payload of GPS?
- Match: nearest, ETA, or first-accept? Consistency: two riders cannot get the same driver.
- Single-region first. Real-time: poll vs pub/sub (I did not ship a broker).
What we design is a ride (or order) as a state machine plus a supply index. States: REQUESTED → MATCHED → EN_ROUTE → IN_PROGRESS → COMPLETED, with CANCELED from the early states. Matching reads a nearby set of available drivers, picks one, and CAS-updates that driver from AVAILABLE to BUSY so two requests cannot take the same driver. Nearby is Standard CS: a grid HashMap from cell to the set of driver ids in that cell, or a geohash prefix → set. Why grid not “query all drivers”: O(all) at 10×. Why not a full GIS lecture: Live Code is the grid plus a state enum. Why not Kafka as box one: I did not operate a cluster. I will mention pub/sub so the driver’s phone sees MATCHED without inventing a Ylogx topic. Ylogx matching is not a thing I shipped — I will not borrow 99.9% as a ride SLA.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
| Ride row + enum state | rideId, rider, driver, state, timestamps | Vs boolean flags that allow illegal pairs | Allowed transitions only | O(1) get/put by rideId |
HashMap<cell, Set<driverId>> or geohash → set | Who is nearby and available | Vs linear scan of all drivers; vs claiming PostGIS I did not ship | Lookup 9 cells (self + neighbors) | O(k) in cell; k ≪ all drivers |
| Driver inventory map | AVAILABLE / BUSY + last cell | Vs storing supply only on the ride | CAS available → busy on match | O(1) |
| Order analogue: SKU count | Remaining units | Same CAS: decrement if count > 0 | Prevents oversell | O(1) per SKU |
| Pub/sub channel (CS mention) | Live location / match events | Not a resume Kafka cluster | Push MATCHED to apps | Fan-out per ride id |
Components (boxes) and who calls whom
Rider app → Ride API → Ride store (state machine)
→ Geo index (grid / geohash)
→ Driver inventory (CAS)
Driver app → location ping updates grid
Optional pub/sub: ride:{id} events (Standard CS, not intern Kafka)
Ylogx is not this diagram. Horizon costmap is rover cells, not Uber.
Rider calls API. API reads geo index, CAS driver, writes ride. Driver pings update the cell map. Dispatcher does not call Kafka unless they asked a message bus; poll is honest if they did not.
APIs
POST /v1/rides { pickup, dropoff } → 201 rideId REQUESTED
POST /v1/rides/{id}/accept driverId → MATCHED (CAS)
POST /v1/rides/{id}/start
POST /v1/rides/{id}/complete
POST /v1/rides/{id}/cancel
PUT /v1/drivers/{id}/location { lat, lon, available }
Order analogue:
POST /v1/orders { sku, qty } → decrement inventory or 409
End-to-end sequence
Happy. Rider POST ride. API computes cell, gathers nearby available drivers, offers or auto-assigns. CAS driver AVAILABLE→BUSY. Ride MATCHED. Driver start → IN_PROGRESS. Complete → driver AVAILABLE, remove from busy, put back in cell set.
Failure — double match. Two rides CAS the same driver: one wins, the other retries the nearby set. If empty: stay REQUESTED or 409 no drivers — block with a status, do not drop the rider silently.
Failure — illegal transition. COMPLETED → MATCHED rejected. Cancel after IN_PROGRESS only if they defined that policy.
In-process Java core
import java.util.*;
enum RideState { REQUESTED, MATCHED, EN_ROUTE, IN_PROGRESS, COMPLETED, CANCELED }
final class Ride {
final String id, riderId;
String driverId;
RideState state = RideState.REQUESTED;
Ride(String id, String riderId) { this.id = id; this.riderId = riderId; }
}
final class RideService {
private final Map<String, Ride> rides = new HashMap<>();
private final Map<String, String> driverStatus = new HashMap<>(); // AVAILABLE|BUSY
private final Map<String, Set<String>> cellToDrivers = new HashMap<>();
private final Map<String, String> driverCell = new HashMap<>();
private long seq;
static String cell(double lat, double lon) {
// coarse grid (Standard CS stand-in for geohash prefix)
return ((int) Math.floor(lat * 100)) + ":" + ((int) Math.floor(lon * 100));
}
synchronized Ride request(String riderId, double lat, double lon) {
Ride r = new Ride("r" + (++seq), riderId);
rides.put(r.id, r);
String c = cell(lat, lon);
String driver = pickAvailable(c);
if (driver != null) {
assign(r, driver);
}
return r;
}
synchronized void ping(String driverId, double lat, double lon, boolean available) {
String old = driverCell.remove(driverId);
if (old != null) {
Set<String> set = cellToDrivers.get(old);
if (set != null) {
set.remove(driverId);
}
}
String c = cell(lat, lon);
driverCell.put(driverId, c);
if (available && "AVAILABLE".equals(driverStatus.getOrDefault(driverId, "AVAILABLE"))) {
driverStatus.put(driverId, "AVAILABLE");
cellToDrivers.computeIfAbsent(c, k -> new HashSet<>()).add(driverId);
}
}
synchronized void cancel(String rideId) {
Ride r = rides.get(rideId);
if (r == null) {
throw new NoSuchElementException(rideId);
}
if (r.state == RideState.COMPLETED || r.state == RideState.IN_PROGRESS) {
throw new IllegalStateException("cannot cancel " + r.state);
}
r.state = RideState.CANCELED;
if (r.driverId != null) {
driverStatus.put(r.driverId, "AVAILABLE");
String cell = driverCell.get(r.driverId);
if (cell != null) {
cellToDrivers.computeIfAbsent(cell, k -> new HashSet<>()).add(r.driverId);
}
}
}
private String pickAvailable(String c) {
Set<String> here = cellToDrivers.getOrDefault(c, Set.of());
for (String d : here) {
if ("AVAILABLE".equals(driverStatus.get(d))) {
return d;
}
}
return null;
}
private void assign(Ride r, String driver) {
String prev = driverStatus.put(driver, "BUSY");
if (!"AVAILABLE".equals(prev) && prev != null) {
driverStatus.put(driver, prev);
throw new IllegalStateException("lost CAS on driver");
}
String c = driverCell.get(driver);
if (c != null) {
Set<String> inCell = cellToDrivers.get(c);
if (inCell != null) {
inCell.remove(driver);
}
}
r.driverId = driver;
r.state = RideState.MATCHED;
}
}
Scale 10× honest
More pings: the grid stays O(k) per cell; I did not shard cells. I did not ship this product. Ylogx Redis −35% is bot cache, CloudFront/ECS/ALB is BI, 99.9% / sub-210 ms are not ride ETAs. Horizon 60 FPS / costmap −55% collision is a rover, not dispatch. Pub/sub mention without a Kafka autobiography. 10×: partition by city cell first, not multi-region S3.
Consistency / uniqueness / drop-vs-block / exactly-once
One driver, one active ride: CAS on inventory. Uniqueness of rideId is the primary key. Location pings may be dropped if stale (newer ping already applied) — drop-vs-block: drop old GPS, block double-assign. Match is not exactly-once messaging; it is an atomic state change. Do not claim Kafka exactly-once.
1 — What did you do? I did not ship Uber. Closest spatial index in spirit: Horizon costmap cells — different domain, say so. 2 — Differently? Offer several drivers and first-accept instead of auto-CAS if they want marketplace fairness. 3 — How do you know the metric? No ride metric on the resume. I will not invent ETAs. Stratify 50+ is models, not trips.
Do not say. “Ylogx was a ride marketplace.” Kafka cluster on the resume. PostGIS as shipped. Multi-region dispatch novel as UTA-default. Job 10454435 will ask Uber.
6. Metrics / logging — ingest, buffer, aggregate, query Standard CS Resume-derived
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Design metrics or logging: ingest, buffer, aggregate, query. Class C / Standard CS as a “Prometheus/ELK” product. Resume analogues, clearly separated: Argus Postgres logs = append-only detection facts (camera, class, confidence, time, box) for 20+ cameras — not Prometheus. Ylogx 30 KPI dashboards +60% ops, sub-210 ms, no LLM on tiles — consuming warehouse KPIs, not building a metrics platform. Streaming error-log moving-average algorithm lives in chapter 22; this card is the system. IQVIA LangSmith traces research runs, not a log product.
Clarify
- Metrics (aggregates) vs logs (raw events) vs traces?
- QPS ingest? Drop vs block when the buffer is full?
- Query: last 5 minutes error rate vs “find this camera’s Tuesday events”?
- Single-region first. Payload size (event row vs video).
What we design is a pipeline: ingest → in-memory buffer → time-bucket aggregates for dashboards → a durable log store for audit queries. Why this shape: operators need O(1) “error rate this minute,” and auditors need “camera 7 at 18:04.” One store that is only a ring buffer loses history; one store that is only raw SQL without buckets makes every dashboard a table scan. Argus shipped the audit half: Postgres append-only events, not a metrics SaaS, not every frame as a row (20×24 FPS would be hundreds of writes per second of pixels). Ylogx shipped the dashboard half as deterministic SQL KPI JSON under RLS, sub-210 ms, LLM off the tile path. I will not merge them into Prometheus. If they want the Live Code moving average over a stream of error logs, that algorithm is chapter 22; here I still show the ring buffer as the in-process core of ingest.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
Time-bucket HashMap<epochMinute, Agg> | count, sum, fail count per minute | Vs re-scan raw logs for every dashboard refresh | KPI tiles; moving window = sum last k buckets | O(1) ingest; O(k) query window |
Ring buffer long[] / circular array | Recent samples or error flags | Vs unbounded ArrayList | Streaming MA / drop-oldest on ingest overflow | O(1) append; O(n) full average or O(1) running sum |
| Append-only log (Argus: Postgres) | Event facts | Vs Prometheus (I did not ship); vs video in DB | Audit: camera + time index | INSERT O(1); query by index |
| Inverted index (CS) term → postings | Search error text | Optional follow-up, not Argus | Find logs containing a token | O(df) merge |
| Ylogx dashboard path | Deterministic KPI JSON | Not this ingest product; not GenAI tiles | CloudFront → ALB → ECS → Postgres | sub-210 ms resume claim |
Components (boxes) and who calls whom
Producers (API, Argus worker) → Ingest API
→ Buffer (ring) — drop stale or block
→ Aggregator (time buckets)
→ Query API (dashboard)
→ Durable log (Postgres facts)
Argus: frame → YOLO → event row → alert / query
Ylogx: browser → CloudFront → ALB → ECS → KPI SQL (no LLM)
No Kafka required to start. No Prometheus claim.
Producers call ingest. Aggregator is a consumer of the buffer. Query API reads buckets (hot) or Postgres (audit). Dashboards do not call the detector.
APIs
POST /v1/ingest { ts, service, level, metric, value, tags }
GET /v1/query?metric=error_rate&from=&to= // buckets
GET /v1/logs?camera=7&from=&to= // Argus-shaped audit
Argus facts (resume, no invented routes):
event { camera, class, confidence, timestamp, box }
End-to-end sequence
Happy — metric. POST ingest. Append ring. Add into current minute bucket. GET query sums last 5 buckets → error rate.
Happy — Argus audit. Detection survives NMS → INSERT event → alert. Dashboard later SELECT by camera and time. Video bytes stay off the event table.
Failure — ingest storm. Ring full: drop oldest / stale (Argus already drops stale frames rather than queue delay) or block producer with 429/503. Say which: safety alerts prefer not to delay; I would drop stale frames, not block the camera pipeline into unbounded RAM. I would not silent-200 then lose the audit INSERT — persist the event or retry.
In-process Java core
System ingest + buckets + ring. Moving-average error-log algorithm detail: chapter 22.
import java.util.*;
final class MinuteAgg {
long count;
long errors;
}
final class MetricsCore {
private final Map<Long, MinuteAgg> buckets = new HashMap<>();
private final int[] ring;
private final int cap;
private int idx;
private int filled;
private long runningSum;
MetricsCore(int ringCap) {
this.cap = ringCap;
this.ring = new int[ringCap];
}
synchronized void ingest(long tsMillis, boolean error) {
long minute = tsMillis / 60_000L;
MinuteAgg a = buckets.computeIfAbsent(minute, k -> new MinuteAgg());
a.count++;
if (error) {
a.errors++;
}
int v = error ? 1 : 0;
if (filled == cap) {
runningSum -= ring[idx];
} else {
filled++;
}
ring[idx] = v;
runningSum += v;
idx = (idx + 1) % cap;
}
/** Last `windowMinutes` error rate from time buckets. */
synchronized double errorRate(long nowMillis, int windowMinutes) {
long end = nowMillis / 60_000L;
long errors = 0, count = 0;
for (int i = 0; i < windowMinutes; i++) {
MinuteAgg a = buckets.get(end - i);
if (a != null) {
errors += a.errors;
count += a.count;
}
}
return count == 0 ? 0.0 : (double) errors / count;
}
/** Streaming MA over the ring (chapter 22 algorithm lives here as the core). */
synchronized double ringAverage() {
return filled == 0 ? 0.0 : (double) runningSum / filled;
}
}
final class ArgusEvent {
final String camera, clazz;
final double confidence;
final long ts;
final int[] box;
ArgusEvent(String camera, String clazz, double confidence, long ts, int[] box) {
this.camera = camera;
this.clazz = clazz;
this.confidence = confidence;
this.ts = ts;
this.box = box;
}
}
/** Append-only facts — resume shape, not Prometheus. */
final class EventLog {
private final List<ArgusEvent> rows = new ArrayList<>();
synchronized void append(ArgusEvent e) { rows.add(e); }
synchronized List<ArgusEvent> byCamera(String camera, long from, long to) {
List<ArgusEvent> out = new ArrayList<>();
for (ArgusEvent e : rows) {
if (e.camera.equals(camera) && e.ts >= from && e.ts <= to) {
out.add(e);
}
}
return out;
}
}
Scale 10× honest
Argus 10× cameras: keep writing compact events, index time and camera, keep video out of Postgres — I did not shard, I did not claim Kubernetes. Ylogx 10× dashboards: pre-aggregate SQL, pool, cache safe KPI JSON, LLM still off the path; 30 is the resume count. I did not ship a metrics vendor. No Kafka. 99.9% is Ylogx serving uptime, not ingest durability nines.
Consistency / uniqueness / drop-vs-block / exactly-once
Audit events: persist then alert (or one transaction) so a page has a row. Metrics buckets are approximate if ingest drops. Ring overflow: drop oldest (stale) vs block — pick. Duplicate ingest: optional event_id unique index (at-least-once pipeline). I do not claim exactly-once Kafka. Ylogx tiles must not show another org’s KPI (RLS), which is stronger than “eventual dashboard.”
1 — What did you do? Argus: Postgres event logs, 20+ cameras, 24 FPS, 73→89% mAP then scale. Ylogx: 30 dashboards, sub-210 ms, +60% ops. Not Prometheus. 2 — Differently? Hold-out camera in eval; for logs, index before 10× SELECT-by-scan. 3 — How do you know the metric? Resume numbers only. I will not invent ingest QPS or p95 bucket lag.
Do not say. “I built Prometheus.” Kafka on Argus. Video in Postgres. LLM on Ylogx tiles. Merge GiftedBooks 99.5% with Ylogx 99.9%. Job 10454435 will ask ELK.
7. Cache Redis-like — dict, LRU, TTL, then separate from Bhavya Live Code IE-asked Resume-derived Standard CS
HLD framing. Full Amazon-retail HLD (Kafka novels, multi-region S3) is not the SDE I UTA default. Independent Rate Limiter SDE I live count = 1 (IE.in 2025-grad, OA+3 system design, not UTA two-DSA). SDE-2 YouTube does not add count 2. If they ask HLD after two lives, answer. Do not volunteer a 40-minute Kafka essay on a two-DSA loop. User-pasted “Common HLD topics” lists without a first-hand URL = Class C / Standard CS.
Stem + label + evidence. Two prompts you must connect then separate. (1) Bhavya Hyd onsite Interview 2: LRU Cache Live Code (HashMap + DLL) — chapter 05. Not Login Tracker (unverified). Not HM Spring login (same IE, different slot). (2) HLD “design Redis” is Class C / Standard CS. Resume: Ylogx is a client of Redis (cache-aside, −35% bot DB latency). Key includes tier. Redis down fail-opens to Postgres on the bot cache; RLS never fail-opens. I did not implement Redis. LFU is a different IE (Reddit 1idtlan), not this card.
Clarify
- Are they asking LC LRU (get/put O(1)) or a cache service (network, TTL, eviction, persistence)?
- QPS? Value size? TTL required? Thread-safety?
- Single process vs N ECS tasks (then the cache is shared Redis, not a JVM HashMap).
- Single-region first. Persistence / replica only if they push.
If they want LRU in Live Code I write HashMap to a node plus a dummy-headed doubly linked list, move-to-front on get and put, evict tail.prev — that is Bhavya, chapter 05, and it is not Login Tracker. If they then say “now a Redis-like cache,” I add a dict of key to node, the same LRU for eviction, lazy TTL on read (expireAt < now → delete), and I say Redis itself runs a single-threaded command loop so INCR and GET are atomic without application locks. Why not ConcurrentHashMap as “I implemented Redis”? That is a multi-thread JVM map with per-bin locks; it is not the Redis product (protocol, persistence, replica). Why not only LinkedHashMap? I mention accessOrder plus removeEldestEntry, then I still write pointers if they want to see the DLL. Ylogx: I am the client. GET key with tier in it; miss → Postgres as RLS role → SET; I did not invent a TTL number. CloudFront is HTTP edge cache for static files, not this dict, and not tenant rows.
Data structures / stores
| Structure or store | What it holds | Why this not that | How it implements the feature | Access pattern / complexity |
HashMap<key, Node> | Value + expireAt + DLL pointers | Vs ArrayList scan O(n) (brute LRU) | O(1) get/put | O(1) average |
| Doubly linked list + dummy head/tail | Recency order MRU↔LRU | Vs TreeMap by timestamp O(log n) | Move-to-front; evict LRU | O(1) splice |
| TTL lazy expire | expireAt on node | Vs background heap of all keys (optional later) | On get, if now ≥ expireAt, remove | O(1) check; expired space until touched or cap eviction |
| Redis product (not intern-built) | Single-thread command loop, optional AOF/RDB | Vs ConcurrentHashMap in each ECS task (N copies, N× limiter bug) | Shared cache for Ylogx bot path | One RTT; −35% bot DB is the resume outcome |
| Ylogx key design | tier/role + query fingerprint + schema version | Vs raw NL key (cross-tier leak) | Cache-aside after RLS success | GET/SET; miss → Postgres |
Components (boxes) and who calls whom
Live Code LRU: Client test → LRUCache.get/put (one JVM)
HLD Redis-like: ECS tasks → Redis (shared dict)
Ylogx bot: NL → Redis GET (tier in key) → miss → Postgres RLS → SET
Ylogx dashboards: not this −35% claim; deterministic SQL, sub-210 ms, no LLM
CloudFront: static GET at edge, not Redis, not RLS JSON
Limiter state also “could” live in Redis — I did not ship a limiter (count still 1)
App calls Redis. Redis does not call Postgres; the app fills on miss (cache-aside). ALB must not point each task at its own HashMap for shared budget or shared cache correctness.
APIs
LRU Live Code:
int get(int key) // -1 miss; counts as use
void put(int key, int val) // evict LRU if over cap
Redis-like:
GET key / SET key val [EX seconds] / DEL key
// single-thread: each command atomic
Ylogx (spoken, not invented TTL):
GET tier | role | qhash | schemaVer
miss → SQL as caller role → SET
Redis down → Postgres (fail-open cache), never skip RLS
End-to-end sequence
Happy — Bhavya LRU. cap=2: put 1, put 2, get 1 (1 becomes MRU), put 3 evicts 2. get 2 → -1.
Happy — Ylogx cache-aside. Bot question. Build key with tier. Redis GET hit → return. Miss → Postgres as role → RLS filters rows → SET → return. −35% bot DB latency is the resume number; no p95, no TTL printed.
Failure — wrong-tier key. Leak. Isolation in the key is the invariant. Failure — Redis down. Fail-open to Postgres on this cache path. Failure — stampede. Many misses; CS: singleflight — I did not ship a named lock. Failure — N JVM LRUs behind ALB. Each replica has its own cap; not a shared cache (and not a shared limiter).
In-process Java core
LRU + lazy TTL. Pointer problem is Bhavya; TTL is the HLD extra. LinkedHashMap mention then still write the DLL if they want pointers.
import java.util.*;
final class LruTtlCache {
static final class Node {
int k, v;
long expireAt; // Long.MAX_VALUE = no TTL
Node prev, next;
Node(int k, int v, long expireAt) {
this.k = k;
this.v = v;
this.expireAt = expireAt;
}
}
private final int cap;
private final Map<Integer, Node> map = new HashMap<>();
private final Node head = new Node(0, 0, Long.MAX_VALUE);
private final Node tail = new Node(0, 0, Long.MAX_VALUE);
LruTtlCache(int cap) {
this.cap = cap;
head.next = tail;
tail.prev = head;
}
synchronized int get(int k, long now) {
Node n = map.get(k);
if (n == null) {
return -1;
}
if (now >= n.expireAt) {
remove(n);
map.remove(k);
return -1;
}
moveFront(n);
return n.v;
}
synchronized void put(int k, int v, long now, long ttlMillis) {
long exp = ttlMillis <= 0 ? Long.MAX_VALUE : now + ttlMillis;
Node n = map.get(k);
if (n != null) {
n.v = v;
n.expireAt = exp;
moveFront(n);
return;
}
n = new Node(k, v, exp);
map.put(k, n);
addFront(n);
if (map.size() > cap) {
Node lru = tail.prev;
remove(lru);
map.remove(lru.k);
}
}
private void moveFront(Node n) {
remove(n);
addFront(n);
}
private void remove(Node n) {
n.prev.next = n.next;
n.next.prev = n.prev;
}
private void addFront(Node n) {
n.next = head.next;
n.prev = head;
head.next.prev = n;
head.next = n;
}
}
/*
* Redis CS (spoken, not intern-built):
* Single-threaded event loop executes GET/SET/INCR one at a time → atomicity
* without app-level synchronized across clients.
* ConcurrentHashMap: multi-thread JVM, not the Redis service.
* Persistence (RDB/AOF) and replica: name if they push; I did not ship them.
*/
Scale 10× honest
Ylogx 10× bot QPS: more safe hit coverage, pool Postgres on miss, keep tier in the key, still no shard claim. I did not shard Redis. −35% is the intern measurement; no Datadog dump. 99.9% and sub-210 ms are the serving path (CloudFront/ECS/ALB), not Redis INFO. I would not edge-cache tenant answers on CloudFront. Implementing Redis-the-product (replication, cluster slots) is beyond intern scope; I stay a competent client.
Consistency / uniqueness / drop-vs-block / exactly-once
Cache is not the source of truth. Stale KPI tile < leaking another org. TTL is eventual; lazy expire can hold dead keys until get or LRU eviction (drop expired on access). SET does not need exactly-once; duplicate SET is idempotent. Redis down: fail-open cache (block would take the bot down). RLS deny: do not SET. Uniqueness of keys is the string including tier — that is the isolation invariant, not a SQL unique index on URLs.
1 — What did you do? Client of Redis, cache-aside, −35% bot DB, tier in the key. LRU code is Bhavya-shaped Live Code, not “I wrote Redis.” 2 — Differently? I would add singleflight on miss if stampede showed up — I will not invent that I shipped it. 3 — How do you know the metric? Resume: −35% bot database latency. No TTL, no p95. Dashboard sub-210 ms is a different path (no LLM).
Do not say. “I implemented Redis.” “Ylogx rate limiter.” Login Tracker as this card. LFU as the same IE. CloudFront cached org JSON. Invented TTL. EKS Redis operator. Job 10454435 will ask Redis internals. Kafka as the cache.
8. What not to say (chapter-wide)
- Full Amazon-retail HLD (Kafka novels, multi-region S3) as if it were the SDE I UTA default.
- Rate Limiter live count 2 from SDE-2 YouTube, Hitesh L5, mentor prep, or elevator DoS.
- LC 8362604 as a second limiter LLD — it was LP “rate limiting decisions.”
- Volunteering a 40-minute Kafka essay on a two-DSA loop.
- Stamping a pasted “Common HLD topics” list as Job 10454435 evidence.
- Inventing a 10454435 question. Filling Arijit’s unnamed SD with TinyURL.
- Ylogx shipped Kafka, S3 product, ride marketplace, rate limiter, or Redis-the-server.
- CloudFront = “I built S3.” Argus Postgres logs = Prometheus.
- Exactly-once I did not ship. EKS-as-shipped. Qdrant, Haar, missed SLA, manager fight.
- InstaRecon exploit / phishing. Login Tracker as verified.
- GiftedBooks 99.5% / sub-300 ms merged with Ylogx 99.9% / sub-210 ms.
LLD companion: chapter 22. Rate Limiter full Java: chapter 06. LRU Live Code: chapter 05. Ylogx serving: chapter 15. Why Redis / CloudFront: chapter 14.