Coding Interview Patterns and Meta Strategies Questions
Recognizing common patterns in interview problems (two-pointer, sliding window, backtracking, divide-and-conquer). Understanding how to approach unfamiliar problems systematically. Meta-strategies include clarifying requirements, starting simple, incrementally optimizing, and thorough testing.
HardSystem Design
75 practiced
Design a distributed deduplication service for streaming events across many nodes. Requirements: duplicates should be removed within a maximum reordering window of 10 seconds; the service must be fault-tolerant (node failures), horizontally scalable, and minimize duplicate emission. Explain partitioning, consistent hashing, replication, durable storage of recent IDs, and how to handle failover.
Sample Answer
Requirements clarification:- Remove duplicates for any events that arrive within a 10s max reordering window.- System must be horizontally scalable and tolerate node failures while minimizing emitted duplicates.- Low-latency (streaming) deduplication.High-level approach:- Partition by event-id hash so each event-id’s deduplication state is owned by a small set of nodes. Use consistent hashing with virtual nodes to balance load and enable smooth scaling.- Replicate each partition to R nodes (e.g., R=3). Use a primary (leader) for low-latency checks and quorum-based durability for writes (W + R-W read quorum), or leaderless with quorum (like Dynamo) depending on latency vs complexity trade-offs.Components:1. Ingest/proxy layer: - Hash event.id -> partition token -> route to partition’s primary (or any replica if leaderless).2. Partition nodes: - Maintain a sliding dedupe-store of recent IDs (10s + small safety margin, e.g., 12s). - Store is an in-memory LRU/TTL cache for speed backed by local durable store (RocksDB / embedded KV) for crash recovery. - Each stored id has: first-seen timestamp, optional metadata, and TTL.3. Replication & durability: - On receiving an event, node checks its local cache: - If ID exists and timestamp within 10s window -> drop duplicate locally and ack suppressed. - If not exists -> write to local cache and synchronous replicate to R-1 replicas (sync or semi-sync with W=2 for availability). - Use write-ahead log (WAL) so pending writes survive crashes. Apply to local RocksDB for persistence.4. Failover & consistency: - Consistent hashing ring with membership via consensus (e.g., etcd) tracks node-to-token mapping. - If primary fails, next replica in ring becomes primary. Since replicas have persisted WAL/RocksDB, they can answer dedupe checks. - Use hinted handoff: writes that couldn’t reach a downed replica are stored and replayed when it returns. - During ownership transfer, new primary replays WALs and reconciles recent IDs (read-repair from other replicas) before serving traffic for that partition.5. Handling ordering window: - Keep TTL = 10s + epsilon; accept that events arriving after TTL are considered new (per requirement). - Optionally maintain a small buffer (per partition) indexed by event timestamp so very-late arrivals within epsilon can still be recognized.6. Minimizing duplicates: - Use idempotent producer semantics where possible (producers tag attempts). - Use synchronous replicate-before-ack for the first-seen write when strong suppression is required; else allow async replicate for higher throughput with slightly higher duplicate risk. - Expose ack semantics to upstream: “accepted and durable” vs “accepted local-only”.7. Operational concerns: - Metrics: duplicate rate, replication lag, WAL lag, cache hit/miss, failover events, per-partition QPS. - SLOs: e.g., 99.9% dedupe effectiveness within 10s window, <100ms median latency. - Autoscaling: add/remove virtual nodes and rebalance via consistent hash; migrate partition state by streaming recent IDs to new owners with short-lived forwarding during handoff. - Testing: chaos testing (node kill, network partitions), load tests covering hotspots; monitor memory for TTL cache.Trade-offs:- Strong consistency (sync replication W=R) minimizes duplicates but increases latency and reduces availability during failures. Quorum/W=2 gives a balance.- Larger replication factor increases fault tolerance and reduces duplicate risk on failover but costs more resources.- TTL strictly enforces 10s boundary; to reduce false-positives on late arrivals beyond 10s would need longer retention but violates requirement.Summary:Use consistent-hash partitioning with replication and WAL-backed per-partition recent-ID stores (in-memory TTL cache + durable RocksDB). On write, check local cache and replicate to replicas; on node failure use ownership transfer + WAL replay + hinted handoff. Tune replication & write quorum to balance duplicate suppression vs latency/availability. Monitor replication lag and duplicate rate, and automate rebalance to keep per-partition load stable.
HardTechnical
80 practiced
Approximate counting at scale: Design a solution to compute the approximate number of unique origin IPs per minute per region for an input rate of 100k events/sec using bounded memory. Explain choice of data structure (e.g., HyperLogLog), mergeability across shards, error bounds, and show Python-like pseudocode to update and merge sketches.
Sample Answer
Approach summary:- Use HyperLogLog (HLL) sketches per (region, minute) bucket. HLL gives fixed, small memory per sketch, mergeability across shards, and tunable relative error: σ ≈ 1.04/√m where m = 2^p.- Shard ingest workers maintain in-memory HLLs for active minute windows and region keys, flush/merge to a central store periodically (or at minute boundary).- Keep time-partitioned keys (region + minute) with TTL to bound retention and memory.Why HLL:- Bounded memory (registers fixed by precision p).- Mergeable: union = element-wise max of registers, ideal for distributed aggregation.- Fast updates (O(1) per event), suitable for 100k events/sec across shards.- Tunable trade-off between memory and error.Sizing example:- Choose p = 14 → m = 16384 registers. Relative error ≈ 1.04/√16384 ≈ 0.81%.- If each register stored in 6 bits → memory ≈ 16384 * 6 bits ≈ 98 KB per sketch. If you have 100 regions × 2 concurrent minutes → ~19 MB memory total (plus overhead).Operational notes:- Maintain per-shard HLLs; at flush time send serialized sketch to aggregator which merges via register-wise max.- Rotate minute buckets; persist merged sketches to long-term store (e.g., RocksDB, S3) for retrospective queries.- Handle IPv6 by hashing full IP string; use a good 64-bit hash (e.g., xxhash64) to map IP to HLL input.- For heavy-tail accuracy (small cardinalities), use HLL with small-range corrections (bias correction / linear counting) as standard libraries do.Python-like pseudocode:Error bounds and SLA:- With p=14, expect ~0.8% stdev; 95% CI roughly ±1.6%. If SLA requires tighter error (e.g., 0.2%), increase p (p=18 → m=262144 → error ≈ 1.04/512 ≈ 0.2%) at cost of ~6× memory.Scaling/throughput:- Each event: one hash + constant-time update — trivial for 100k/sec across many workers.- Network: send one sketch per (region, minute) per shard once per minute; merging is cheap.Edge cases:- Very small cardinalities: use linear counting fallback.- Clock skew: use ingestion timestamps and consistent bucketing.- Verification: periodically sample exact counts on low-volume traffic to validate error.This design provides mergeable, memory-bounded approximate unique counts per-minute per-region with predictable error and operational simplicity suitable for SRE use.
python
# simple HLL wrapper pseudocode
from xxhash import xxh64
class HLL:
def __init__(self, p=14):
self.p = p
self.m = 1 << p
self.registers = [0] * self.m
def _rho(self, w, max_bits):
# count leading zeros in w (1-based)
return min(max_bits, (w.bit_length() ^ (1 << (max_bits-1))).bit_length()) # placeholder
def add(self, ip):
h = xxh64(ip).intdigest() # 64-bit hash
idx = h & (self.m - 1) # lower p bits
w = h >> self.p # remaining bits
rank = self._rho(w, 64 - self.p)
self.registers[idx] = max(self.registers[idx], rank)
def merge(self, other):
assert self.p == other.p
for i in range(self.m):
if other.registers[i] > self.registers[i]:
self.registers[i] = other.registers[i]
def count(self):
# apply HLL bias correction and alpha_m constants (omitted details)
Z = 1.0 / sum(2 ** (-r) for r in self.registers)
E = alpha(self.m) * (self.m ** 2) * Z
# small-cardinality correction (linear counting) and large range correction applied here
return corrected_E(E)MediumTechnical
81 practiced
Given a stream of (timestamp, latency) pairs for a single host, implement a Python function that returns the longest contiguous period (by time) where the average latency <= threshold. Assume timestamps are increasing. Aim for O(n) time and O(1) extra space. Explain how you handle variable-length windows and report start/end timestamps.
Sample Answer
Approach: use a two-pointer sliding window over the ordered (timestamp, latency) list. Expand the right pointer adding latencies; while the window's average > threshold, move left pointer forward subtracting latencies. Track the longest-duration window (end_ts - start_ts) that satisfies average <= threshold. This is O(n) time and O(1) extra space (only scalars and indices).Key points:- Variable-length windows: window size adapts by expanding right and contracting left to maintain average constraint.- We measure "longest by time" using timestamps difference (end_ts - start_ts), not number of samples.- O(n) time: each index enters/exits window once. O(1) extra space: only scalars and indices used.Edge cases:- If you need time-weighted average (latency sample represents interval lengths), replace simple mean with weighted sum by durations between timestamps.- Timestamps equal or irregular: function assumes strictly increasing timestamps; handle equal timestamps by defining duration convention (e.g., 0).
python
from typing import List, Tuple, Optional
def longest_period_under_threshold(samples: List[Tuple[int, float]], threshold: float
) -> Optional[Tuple[int, int]]:
"""
samples: list of (timestamp, latency), timestamps strictly increasing
threshold: maximum allowed average latency (per-sample average)
Returns (start_ts, end_ts) inclusive timestamps of longest contiguous period by time,
or None if no window meets the threshold.
"""
n = len(samples)
if n == 0:
return None
left = 0
sum_lat = 0.0
best_start = best_end = None
best_duration = -1
for right in range(n):
sum_lat += samples[right][1]
# shrink while average exceeds threshold
while left <= right and (sum_lat / (right - left + 1)) > threshold:
sum_lat -= samples[left][1]
left += 1
# now window [left..right] has avg <= threshold (or left > right => empty)
if left <= right:
start_ts = samples[left][0]
end_ts = samples[right][0]
duration = end_ts - start_ts
if duration > best_duration:
best_duration = duration
best_start, best_end = start_ts, end_ts
return (best_start, best_end) if best_start is not None else NoneMediumTechnical
77 practiced
Testing distributed systems: You have implemented a distributed rate limiter. Outline a test plan covering unit tests, integration tests, chaos tests, and performance tests. Include test cases for concurrency correctness, clock skew, network partitions, and recovery from partial failures. What tooling and metrics would you use to validate behavior?
Sample Answer
Situation: As the SRE responsible for a distributed rate limiter, I’d validate correctness, resilience, and performance across unit, integration, chaos, and performance tests. Below is a concise, actionable test plan.Unit tests- Validate core algorithms deterministically: token-bucket/leaky-bucket logic, quota windows, increment/decrement, expiry.- Concurrency correctness (single-process): run tight multi-threaded tests with mocked clocks to ensure atomic counters and boundary behavior (e.g., exactly N allowed per window).- Edge cases: zero/negative limits, very large rates, bursts, TTL overflow, invalid inputs.- Tools: language test framework + property-based testing (Hypothesis/quickcheck).Integration tests- Multi-node functional tests in CI using containers (Docker, docker-compose or Kubernetes): leader election, state replication, client SDK behavior.- Test cases: - Consistency under concurrent requests from multiple nodes (simulate many clients issuing requests to different nodes). - Persistence: restart nodes, verify counters persist/restore. - Clock skew: inject synthetic clock offsets per node (±500ms—±5s), validate global enforcement.- Tools: pytest/Go test, test harness that can drive many clients, use real datastore (Redis/etcd/Cockroach).Chaos tests- Network partitions: partition nodes into two groups; verify safety (no double-allow beyond global quota) and liveness trade-offs; assert no silent over-issuance.- Partial failures: kill leaders, blackhole packets, slow network (tc/netem). Verify failover and correctness during and after recovery.- Clock drift amplification: simulate NTP failures causing large skews; observe behavior.- Tools: Chaos Monkey/chaos mesh/gremlin, tc, iptables.Performance tests- Load tests: ramp to peak QPS + 2x, measure p95/p99 latencies, error rates, saturation points.- Burst tests: sudden spikes to verify smoothing and backpressure behavior.- Scale tests: increase node count and client count to validate horizontal scaling and coordination overhead.- Tools: wrk/jmeter/fortio/locust, Grafana + Prometheus for metrics collection.Specific test cases (concise)- Concurrency correctness: 1000 concurrent clients issue 1 request each against distributed nodes with global quota 500 → assert exactly 500 allowed, rest rejected.- Clock skew: offset some nodes by +3s, -3s; run tight windows and ensure no violations.- Network partition: split cluster; during split ensure combined allowed <= quota; after heal, reconcile counters without double-count.- Recovery from partial failures: kill leader, bring it back with stale state; assert reconciliation converges and no retroactive double allows.Metrics and validation- Functional metrics: allowed_count, denied_count, throttle_rate, reconcile_conflicts.- Reliability metrics: error_rate, leader_election_count, node_restarts.- Performance metrics: request_latency_p50/p95/p99, throughput (req/s), CPU/memory, disk I/O.- Correctness checks: monotonicity assertions (global allowed never exceeds quota over sliding window), and end-to-end client-visible SLOs.- Observability: Prometheus for metrics, Grafana dashboards, distributed tracing (Jaeger/Zipkin) to follow requests across nodes, and logs aggregated to ELK for forensic checks.Automation and CI- Run unit + fast integration on every PR.- Nightly chaos and performance pipelines with synthetic load and automatic assertions; fail build or raise incidents when SLOs violated.- Canary rollout with traffic shadowing and realtime metric-based gates before full deployment.This plan ensures algorithmic correctness, consistency under concurrency, resilience to network/clock faults, and capacity to meet SLOs in production.
HardTechnical
79 practiced
Case study: You have a large, untriaged alert queue. Design an algorithm to automatically prioritize alerts using minimal historical labeling. Describe features you would extract (service, error code, recent history), an algorithmic approach (semi-supervised learning with active learning), and how to integrate the prioritizer with on-call workflows to ensure human feedback improves the model over time.
Sample Answer
Requirements & constraints:- Prioritize high-impact, actionable alerts with minimal labeled history.- Low false negatives for critical incidents; explainability for on-call trust.- Real-time scoring, human-in-the-loop feedback, gradual model updates.Feature engineering (examples):- Static: service name, team owner, alert type, severity level, error code, SLO violation flag.- Temporal: alert rate spike (delta over 5/15/60m), time since first occurrence, repeat count in window.- Contextual: recent deploys, correlated alerts across services, host/container metadata, user-facing vs internal path.- Derived: anomaly score from metrics, estimated affected hosts, past incident frequency for this signature.Algorithmic approach:- Build an embedding for alert signatures (categorical encodings + numeric features; use LightGBM or small MLP).- Semi-supervised pipeline: train initial model on limited labeled incidents (critical vs noise) + pseudo-label high-confidence predictions from unsupervised clustering (HDBSCAN) and isolation-forest anomaly scores.- Active learning: uncertainty sampling (entropy or margin) to surface ambiguous alerts to on-call for labeling; prioritize queries that maximize model-change (expected model output change).- Calibrate probabilities (Platt/ isotonic) so scores map to risk thresholds.Human-in-loop integration:- Integrate into alert router: score each alert and insert priority tag; push high-risk alerts with enriched context to pager and lower risk to a triage queue/Slack channel.- UI: show model reason (top contributing features) and allow quick labels (critical/ignore/handled) and incident link.- Workflow: on-call labels are batched; labels flow to a labeling queue. Use TTL for stale labels and require periodic review of model suggestions.- Feedback loop: nightly retrain with new labels + semi-supervised pseudo-labels; use validation set for drift detection. Deploy model behind feature-flag; rollback if false-negative rate increases.- Guardrails: threshold tuned to SLO error budget; human override always wins; alert sampling for audits.Metrics & monitoring:- Precision/recall on critical incidents, time-to-ack reduction, false-negative rate, human label throughput, model confidence distribution, concept drift alerts.- Runblame: link model decisions to retraining data to avoid feedback loops.Trade-offs:- Start simple (tree-based, explicit features) for explainability; move to embeddings if scale requires.- Balance automation speed with on-call trust by gradual rollout and transparent explanations.
Unlock Full Question Bank
Get access to hundreds of Coding Interview Patterns and Meta Strategies interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.