Algorithm Design & Real-Time System Optimization Questions
Algorithm design techniques and real-time optimization strategies applicable to distributed systems and latency-sensitive architectures. Covers scheduling, resource management, concurrency, distributed algorithms, load balancing, and performance optimization under strict latency requirements.
MediumTechnical
55 practiced
Design a leader election protocol for a set of stateless processors in a single region without using an external coordination service. Outline failure detection, lease duration and renewal, fencing to avoid stale leaders, and how to avoid split-brain during network partitions.
Sample Answer
Approach summary:Use a lease-based leader election built on a strongly-consistent metadata store you already run for pipelines (RDBMS with serializable transactions or a cloud K/V with conditional writes). Processors remain stateless; leadership is represented by a single row/key with owner-id, epoch (monotonic token), lease_expiry, and optional heartbeat_ts.Failure detection and lease acquisition:- To become leader, a node performs an atomic conditional write: if lease_expiry < now OR owner_id is null, set owner_id = my_id, epoch = epoch+1, lease_expiry = now + TTL (CAS/conditional update). This ensures single-writer semantics without a separate coordination service.- Heartbeats are implicit via lease renewal: the leader attempts renewal periodically before expiry (see next).Lease duration and renewal:- Choose TTL = 3 * max_p99_rpc_latency (or 3x expected jitter). This balances fast failover and false positives.- Renew at interval R = TTL / 3 (or sooner) to allow retries. Renewal is another conditional update that succeeds only if current owner_id == my_id and epoch unchanged.- On renewal failure, the node stops leader duties immediately.Fencing to avoid stale leaders:- Use the epoch/token stored atomically in the lease row. Every operation that requires leader privileges must include the epoch (or read lease row and verify owner_id+epoch+expiry) — reject operations if epoch mismatches or lease expired.- For stronger safety, attach the epoch token to external writes (e.g., pipeline metadata, output filenames) so readers/writers can detect stale producers.Avoiding split-brain during network partitions:- Rely on the strong consistency of the chosen metadata store (serializable/conditional writes). Only one node can hold the lease by atomic CAS, so partitions where two nodes think they’re leader are prevented if the store remains reachable from only one partition.- If the store is temporarily unreachable from all nodes, nodes cannot acquire/renew leases; they must step down when their lease expires, ensuring no two partitioned leaders persist.- If your environment allows multiple availability zones but single region, prefer the metadata store deployed in multiple AZs with quorum so that a minority partition cannot hold the lease.Trade-offs and notes:- This design depends on a strongly-consistent shared store; it avoids ZK/etcd but uses existing durable metadata storage.- TTL tuning: shorter gives faster failover but more false leader changes; longer reduces churn.- For extremely low-latency or geo-distributed setups, consider augmenting with quorum-based leader election (Paxos/Raft) or using consensus libraries. For typical data pipelines in one region, lease+fencing with atomic conditional writes is simple, robust, and operationally lightweight.
EasyTechnical
61 practiced
Explain approximate streaming algorithms such as HyperLogLog and Count-Min Sketch. For real-time unique-user counts and frequent-item detection at very high throughput, discuss accuracy vs memory vs update latency trade-offs and how to merge state across partitions with minimal impact on latency.
Sample Answer
HyperLogLog (HLL) and Count-Min Sketch (CMS) are compact, mergeable probabilistic structures suited for high-throughput streaming.HyperLogLog — approximate cardinality:- Idea: hash each element and record the maximum number of leading zeros in many buckets; aggregate gives an estimate of distinct count with small bias correction.- Strengths: very low memory (e.g., 1–12 KB for millions of uniques), mergeable by taking per-bucket maxima.- Trade-offs: error ~1.04 / sqrt(m) (m = number of registers). No ability to list which items are unique, only count.Count-Min Sketch — approximate frequency:- Idea: multiple hash functions map items to counters in rows; update increments counters; query returns min across rows as an upper-bound estimate of frequency.- Strengths: fixed small memory, O(1) updates and queries, mergeable by element-wise counter addition.- Trade-offs: overestimates due to hash collisions; error bound ε with probability δ depends on width/depth (memory ~ O(1/ε * log 1/δ)). No deletions unless using conservative/decrement variants.Accuracy vs memory vs update latency:- Memory up => lower error. Doubling registers (HLL) or width/depth (CMS) reduces error but increases cache footprint and network transfer.- Latency: both have O(1) updates; but large sketches may not fit L1/L2 cache, increasing per-update latency. Choose parameters to fit in cache for lowest latency.- For strict tail-latency, favor smaller sketches and accept higher error; for accuracy-critical metrics, allocate more memory and accept slightly higher update cost.Merging across partitions with minimal latency:- Use mergeable nature: maintain per-partition sketches and periodically merge (HLL: per-register max; CMS: element-wise add).- To minimize impact: - Merge asynchronously in background jobs or via hierarchical aggregation (local → regional → global) to spread CPU/network. - Use delta-updates: send only changed buckets or compressed deltas (sparse encoding when updates sparse) to reduce network. - Choose checkpoint frequency balancing staleness vs merge cost (e.g., 1–60s windows). - Keep sketches small enough to transmit in a few ms; prefer binary serialized forms and use RDMA / efficient RPCs.- For strong consistency needs, use CRDT-style merges and accept slightly higher ingestion path latency only for control-plane operations; keep the hot path append-only increments local.Example parameter guidance:- Realtime unique users at 99% acceptable error: HLL with 2^12 registers (~4 KB) gives ~1.6% error.- Frequent-item detection for top-k with ε=0.01, δ=0.01: CMS width ≈ 1/ε =100 counters per row, depth ≈ ln(1/δ) ≈5.In summary: pick HLL for uniques, CMS for heavy-hitters; tune memory to hit your error SLA while keeping sketch sizes cache-friendly; merge asynchronously and hierarchically, and transmit compressed deltas to minimize latency.
MediumTechnical
64 practiced
Compare Raft (leader-based) vs leaderless/gossip approaches for coordinating offsets and lightweight configuration among stream-processing workers. Evaluate latency, write availability, conflict resolution complexity, and operational costs for a low-latency pipeline.
Sample Answer
Framework: compare along the four requested axes and finish with a practical recommendation for a low-latency stream pipeline.Summary comparison- Raft (leader-based): single elected leader serializes updates; strong consistency (linearizability) when writes are committed to quorum.- Leaderless/gossip: any node can accept writes, state propagates via anti-entropy/gossip; typically eventual consistency unless using CRDTs.Latency- Raft: commit latency ≈ 1 round-trip (leader -> followers quorum) + leader processing. Predictable tail latency but the leader is a single hop for all writes; leader overload or network hiccup increases tail latency.- Gossip: low median local-write latency (local accept), reads may be stale or require read-repair/quorum reads (higher latency). Convergence time is O(log N) gossip rounds; not bounded like Raft.Write availability- Raft: leader failure means a short period of unavailability while a new leader is elected; writes require leader/quorum so can be blocked in split-brain or minority partition.- Gossip: higher write availability — nodes can accept writes during partitions; eventual convergence later. Good for always-available ingestion hotspots.Conflict-resolution complexity- Raft: low — deterministic, single-writer sequencing avoids conflicts.- Gossip: high — must resolve concurrent updates (application-level merges, CRDTs, last-write-wins, vector clocks). For offsets/configs, CRDTs or careful merge rules needed to avoid incorrect checkpoints.Operational costs- Raft: operationally simpler to reason about; mature tools (etcd/consul) and metrics. Costs include leader sizing, monitoring, predictable recovery procedures. Network load concentrated on leader.- Gossip: tuning anti-entropy intervals, dealing with divergent state, more complex debugging and testing. Might require more cross-node bandwidth but avoids leader hotspots.Practical recommendation for a low-latency stream pipeline (Data Engineer perspective)- Use Raft (etcd/raft) for critical global state that requires correctness: committed offsets, checkpoint ownership, job/worker assignments. Benefits: simple correctness, easy to reason about exactly-once or committed progress; latency is predictable and acceptable if leader is well-sized and co-located.- Use gossip for high-frequency, local, and best-effort metadata: worker heartbeats, local load signals, metrics, or soft config flags where temporary divergence is acceptable. This minimizes write hops and keeps ingestion latency low.- Hybrid pattern: local fast-path writes to in-memory/local store; on successful processing publish offset to leader-backed Raft store asynchronously or in batches to amortize commit cost. For low-latency guarantees, keep leader and workers in same AZ/rack and use small quorum (3 nodes) to reduce RTTs.- If choosing leaderless for offsets, invest in CRDT-based designs or strong merge rules and accept added complexity and testing cost.Key tradeoffs: Raft = stronger correctness, simpler conflict handling, predictable latency but single-leader bottleneck; Gossip = higher availability and lower local write latency, but complex conflict resolution and operational overhead.
MediumTechnical
91 practiced
Implement a simple adaptive rate limiter in Go that adjusts allowed QPS per tenant based on observed response latency using an additive-increase/multiplicative-decrease (AIMD) scheme. Provide concurrency-safe APIs, how you measure latency, and describe stabilization controls to avoid oscillation.
Sample Answer
We can implement a per-tenant adaptive rate limiter in Go using an AIMD controller that adjusts allowed QPS based on observed response latency. We'll use a token-bucket for enforcement, an exponential moving average (EMA) of service latency for measurement, and AIMD rules executed periodically or on reported samples. The APIs are concurrency-safe using mutexes/atomics.Key points:- Latency measured via EMA (smooths noise); alpha ~0.1–0.3 recommended.- AIMD: small fixed additive increase when latency ≤ target, multiplicative decrease when latency above target to react quickly to overload.- adjustInterval prevents oscillation (only change rate at controlled cadence).- Additional stabilization: hysteresis (different thresholds for increase/decrease), require multiple consecutive breaches before decreasing, or cap rate-of-change per adjustment.Complexity: per-Allow O(1). Concurrency: single mutex per-tenant; scale by sharding/embedding per-tenant limiters in sync.Map. Edge cases: cold-start (EMA=0), bursts (bucket capacity), tenant starvation—enforce minQPS.
go
package adaptive
import (
"sync"
"time"
"math"
)
// TenantLimiter controls QPS for one tenant.
type TenantLimiter struct {
mu sync.Mutex
// token bucket
tokens float64
lastRefill time.Time
capacity float64 // max tokens == allowed QPS
refillPerSec float64 // equals allowed QPS
// latency measurement (EMA)
latencyEMA float64
emaAlpha float64
// AIMD params
additiveInc float64 // QPS add when healthy
multShrink float64 // factor to multiply when high latency
targetLatency time.Duration
minQPS, maxQPS float64
// stabilization
lastAdjust time.Time
adjustInterval time.Duration
}
// NewTenantLimiter initializes limiter with initialQPS.
func NewTenantLimiter(initialQPS float64, targetLatency time.Duration) *TenantLimiter {
now := time.Now()
return &TenantLimiter{
tokens: initialQPS,
lastRefill: now,
capacity: math.Max(1, initialQPS),
refillPerSec: initialQPS,
latencyEMA: 0,
emaAlpha: 0.2,
additiveInc: 1.0,
multShrink: 0.7,
targetLatency: targetLatency,
minQPS: 1.0,
maxQPS: 10000,
lastAdjust: now,
adjustInterval: 1 * time.Second,
}
}
// Allow attempts to take one token. Non-blocking.
func (t *TenantLimiter) Allow() bool {
t.mu.Lock()
defer t.mu.Unlock()
now := time.Now()
elapsed := now.Sub(t.lastRefill).Seconds()
t.tokens += elapsed * t.refillPerSec
if t.tokens > t.capacity {
t.tokens = t.capacity
}
t.lastRefill = now
if t.tokens >= 1.0 {
t.tokens -= 1.0
return true
}
return false
}
// Report records observed latency and possibly adjusts allowed QPS.
// Call Report after each request completes.
func (t *TenantLimiter) Report(latency time.Duration) {
t.mu.Lock()
defer t.mu.Unlock()
// update EMA latency
l := float64(latency.Milliseconds())
if t.latencyEMA == 0 {
t.latencyEMA = l
} else {
t.latencyEMA = t.emaAlpha*l + (1-t.emaAlpha)*t.latencyEMA
}
// only adjust at most once per adjustInterval
if time.Since(t.lastAdjust) < t.adjustInterval {
return
}
t.lastAdjust = time.Now()
curQPS := t.refillPerSec
target := float64(t.targetLatency.Milliseconds())
if t.latencyEMA <= target {
// additive increase
curQPS += t.additiveInc
} else {
// multiplicative decrease
curQPS = math.Max(t.minQPS, curQPS * t.multShrink)
}
// clamp
if curQPS > t.maxQPS {
curQPS = t.maxQPS
}
t.refillPerSec = curQPS
t.capacity = math.Max(1.0, curQPS) // bucket capacity ~ QPS
}MediumTechnical
63 practiced
Describe the internals of the circuit breaker pattern: metrics to monitor (error rate, latency), failure thresholds, sleep window, and half-open probe strategy. For an external enrichment API with variable latency, how would you choose parameters to minimize end-to-end tail latency without losing availability?
Sample Answer
Start with the basics: a circuit breaker tracks recent calls to an external API and flips between CLOSED (normal), OPEN (fail-fast), and HALF‑OPEN (probe). Key internals and metrics:- Metrics to monitor: - Error rate (count or percentage of failures) over a sliding window (time-based or rolling-count). - Latency percentiles (p50/p95/p99) and request timeouts — tail latency matters more than mean. - Request volume (to avoid noisy signals at low traffic).- Failure thresholds: - Use a minimum request volume (e.g., 50 requests) plus an error-rate threshold (e.g., 5–10%) to trip; avoids tripping on small-sample noise. - Consider separate thresholds for hard failures (5xx, timeouts) vs. soft errors (4xx).- Sleep window: - How long to stay OPEN before trying probes — choose based on expected recovery time of the external API and SLOs (typical 5–30s). Too short causes flapping; too long reduces availability.- Half‑open probe strategy: - Limit concurrent probes (e.g., 1–5 requests) and use success/failure counters over a small sample to decide to close again. - Exponential ramp-up of probes if successes continue.For a variable-latency enrichment API and a goal of minimizing end-to-end tail latency while keeping availability:- Enforce strict client-side timeouts slightly below your pipeline SLA for that call (e.g., if enrichment must finish in 200ms, set timeout 150–180ms).- Treat timeouts as failures for the breaker.- Use latency-aware tripping: include p95 or p99 latency exceeding a threshold as a trigger (not just error rate).- Use a short sliding window for latency detection (e.g., 30–60s) so the breaker reacts quickly to spikes.- Set a minimum volume so spikes at low QPS don't open the breaker.- On OPEN, return a fast fallback (cached enrichment, default values, or mark as missing) to preserve pipeline throughput and avoid cascading backpressure.- Combine with retries only for idempotent calls and with capped backoff; avoid retry amplification when breaker is OPEN.- Monitor metrics, iterate: tune thresholds by observing p99 latency and error correlations; if breaker opens too often, increase min volume or widen window; if tail latency spikes, lower timeout or tighten latency threshold.- Add bulkheads/isolation so one noisy enrichment call doesn’t exhaust resources across pipelines.This balances minimizing tail latency (fail fast + fallback + latency-aware tripping) with availability (controlled probe strategy, minimum volume, and graceful degradations).
Unlock Full Question Bank
Get access to hundreds of Algorithm Design & Real-Time System Optimization interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.