Designing a Live Polling & Q&A System for Large-Scale Virtual Events

Designing a Live Polling & Q&A System for Large-Scale Virtual Events

Designing a Live Polling & Q&A System for Large-Scale Virtual Events

A production-grade walkthrough of real-time polling and Q&A for events with tens of thousands of concurrent attendees — ingest, sharded aggregation, push-based fan-out, reliability, and the interview-style deep dives that separate a good answer from a great one.

01

Introduction & History

Live polling and Q&A systems let a presenter ask a question to a large audience and see aggregated results appear on screen within a second or two, while attendees simultaneously submit and upvote questions for the speaker. What looks like a simple feature — a bar chart that updates itself — is actually a distributed systems problem that combines high-fan-in write traffic, high-fan-out read traffic, real-time aggregation, and strict ordering and consistency guarantees, all under a hard latency budget.

The category traces its roots to classroom “clicker” devices used in the 1990s and 2000s, where students pressed physical remotes to answer multiple-choice questions and a base station tallied the results. As internet connectivity became ubiquitous, this evolved into web and mobile based tools. Products like Poll Everywhere, Mentimeter, and Slido popularized browser-based live polling for conferences and classrooms in the 2010s. When the COVID-19 pandemic forced conferences and town halls to go fully virtual almost overnight, these tools had to scale from hundreds of participants in a room to tens of thousands of attendees spread across the globe, joining from browsers, mobile apps, and embedded webinar platforms simultaneously — a jump of two to three orders of magnitude in a very short time.

1990s–2000s

Classroom clickers

Students press physical remotes to answer multiple-choice questions; a base station tallies results in the room — the earliest ancestor of today’s live polling.

2010s

Browser-based polling

Poll Everywhere, Mentimeter, and Slido popularize web polling for conferences and classrooms — still mostly hundreds of participants per event.

2020s

Virtual-event scale

Pandemic-era virtual conferences push the same tools from hundreds to tens of thousands of concurrent attendees — a two-to-three-order-of-magnitude scaling jump.

That scaling jump exposed the real engineering challenge: it is easy to build a poll that works for 50 people using a single server and a SQL UPDATE ... SET count = count + 1 statement. It is extremely difficult to build one that works for 50,000 people voting within the same 10-second window, where every single vote should be reflected in an aggregated result broadcast to every attendee’s screen with sub-second latency, without melting your database.

Real-life analogy

Picture a stadium full of fans holding up colored cards during an interval quiz. A single scorekeeper trying to count every card individually would fall hopelessly behind. Instead, section captains tally their own block, then a central scoreboard sums the sections. That’s exactly how sharded aggregation works — and why a naive single-counter design collapses at scale.

?
What the interviewer may be probing for
  • Do you understand why this is a “hard” system design problem rather than a CRUD app with a UI on top?
  • Can you articulate the difference between a write-heavy fan-in problem (votes) and a read-heavy fan-out problem (broadcasting results) happening in the same system?
  • Do you know where the industry’s mental model of “clicker to real-time distributed counters” comes from, showing you understand the problem’s evolution, not just today’s tools?
02

Problem Framing & Requirements

Before drawing boxes and arrows, a senior architect nails down what “large-scale virtual event” actually means numerically, because the design decisions for 500 concurrent users are entirely different from those for 50,000.

Functional Requirements

  • Presenters can create polls (multiple choice, word cloud, rating scale, open text) tied to a live event/session.
  • Attendees submit votes/answers in real time; each attendee can vote once per poll (or per allowed number of times, e.g. multi-select).
  • Aggregated results (counts, percentages, word cloud weights) update on presenter and attendee screens in near real time.
  • Attendees submit Q&A questions; other attendees upvote questions; the moderator/presenter sees the ranked question list live.
  • Moderators can moderate content (hide/delete inappropriate questions, close polls).
  • Historical results are available after the event for export/reporting.

Non-Functional Requirements

  • Scale: 50,000+ concurrent attendees in a single session; bursts of tens of thousands of votes within a 5–10 second window when a poll opens.
  • Latency: Aggregated result updates should reach clients within roughly 500ms–2s of a vote being cast (perceived as “instant” but not literally real time to the millisecond).
  • Consistency: Exactly-once (or effectively-once) counting per attendee per poll — no double counting, no silently dropped votes; approximate/eventually-consistent aggregate display is acceptable, but the final tally after poll close must be exact.
  • Availability: The system must not go down mid-event; events are scheduled, non-negotiable, and failures are highly visible (a stalled poll on a 20,000-person livestream is a very public incident).
  • Fan-out efficiency: One aggregated update must reach tens of thousands of open connections without linear cost per attendee on the write path.
  • Abuse resistance: Prevent bots/scripts from stuffing votes or spamming Q&A.
i
Framing device

Separate the problem into three sub-problems that have different scaling shapes — (1) ingest (many small writes arriving concurrently), (2) aggregate (combining those writes into a compact summary), and (3) fan-out (delivering that summary to many readers cheaply). Most of the interesting design decisions live at the boundaries between these three stages.

Sizing the Problem in Numbers

System design interviews reward turning vague requirements into a rough numeric model early. A reasonable working set of assumptions for “a large virtual event”:

DimensionAssumptionImplication
Concurrent attendees50,00050,000 concurrent WebSocket connections to maintain
Poll participation rate~70–80% of attendees vote on a given poll~35,000–40,000 votes per poll
Vote submission windowMost votes land within the first 10–15 seconds of a poll openingPeak ingest rate can be 4,000–8,000+ votes/sec, not the smoothed average
Result update cadenceEvery 500ms–1s while a poll is openFan-out layer must push ~1–2 broadcasts/sec × 50,000 connections
Q&A questions per sessionHundreds to low thousands, with upvotes numbering in the tens of thousandsUpvotes behave like a second, parallel high-fan-in stream needing the same treatment as votes
Event duration30 minutes to several hoursConnections must be held reliably for extended periods, including across brief network hiccups

These numbers matter because they immediately rule out naive approaches: 8,000 writes/sec against a single relational row, or 50,000 clients polling an HTTP endpoint every second (which alone is 50,000 req/sec of pure waste), are both non-starters at this scale — which is exactly why the architecture below leans on stream processing and push-based fan-out from the outset rather than retrofitting them after a proof-of-concept falls over.

50kconcurrent attendees
~40kvotes per poll
4–8k/speak ingest
≤ 2svote→broadcast
03

Architecture & Components

At a high level, the system separates the “hot path” (votes flowing in, results flowing out, both needing to be fast and horizontally scalable) from the “control path” (poll creation, moderation, historical reporting, which can tolerate normal request/response latency and standard CRUD patterns).

CLIENTS Attendee Browsers / Apps Presenter Console EDGE CDN / Static Asset Edge Global Load Balancer EDGE GATEWAY API Gateway (REST/HTTP) WebSocket Gateway Cluster INGEST LAYER Vote Ingest Service Rate Limiter / Abuse Q&A Ingest Service EVENT STREAMING BACKBONE Partitioned Message Broker (Kafka / Kinesis) AGGREGATION Stream Aggregation Workers Redis Aggregate Store FAN-OUT Pub/Sub Broadcast Service DURABLE STORAGE Vote Log (Cassandra / DynamoDB) Metadata DB (PostgreSQL) Analytics (ClickHouse / BigQuery)
Fig. 1 — End-to-end architecture. Solid arrows show the real-time hot path (ingest → broker → aggregation → fan-out → gateway → clients). Dashed arrows show async durable writes and control-path flows.

Component Breakdown

  • CDN / Edge: Serves static assets (JS bundles, poll UI) close to attendees globally, reducing load-time latency and offloading the origin.
  • Global Load Balancer: Routes attendees to the nearest healthy regional cluster; performs TLS termination and DDoS scrubbing.
  • API Gateway (REST/HTTP): Handles stateless request/response operations — poll creation, fetching poll definitions, authentication, historical data.
  • WebSocket Gateway Cluster: Holds the long-lived, persistent connections attendees use to receive real-time updates (or Server-Sent Events / long-polling as a fallback). This tier is deliberately stateless regarding business logic — it only manages connections and subscriptions to broadcast topics.
  • Rate Limiter / Abuse Filter: Sits in front of ingest to reject duplicate votes, throttle per-connection request rates, and catch obvious bot patterns before they hit the streaming backbone.
  • Vote / Q&A Ingest Services: Stateless services that validate a vote or question (Is the poll open? Has this attendee already voted? Is the attendee actually registered for this event?) and publish an event to the streaming backbone.
  • Partitioned Message Broker (Kafka/Kinesis-style): The backbone that decouples ingest from aggregation. Votes for a given poll are partitioned (e.g., by poll_id) so ordering per poll is preserved and aggregation workers can consume in parallel across partitions.
  • Stream Aggregation Workers: Consume the vote/question stream and maintain running aggregates (counts per option, top-N ranked questions) using approximate, mergeable data structures.
  • In-Memory Aggregate Store (Redis Cluster): Holds the current “hot” aggregate for each active poll, using atomic counter/sorted-set operations for very low-latency reads and writes.
  • Pub/Sub Broadcast Service: Takes a computed aggregate snapshot (or delta) and fans it out to every WebSocket gateway node subscribed to that poll’s topic, which then pushes to individual client connections.
  • Durable Vote Log (Cassandra/DynamoDB-style wide-column store): The append-only source of truth for every individual vote/question — used for exact final tallies, audits, and replay/recovery.
  • Metadata DB (PostgreSQL): Stores structured, low-volume, relational data — events, sessions, polls, users, moderation state.
  • Analytics Store (ClickHouse/BigQuery): Receives the full event stream for post-event reporting, funnel analysis, and export — decoupled from the real-time path so heavy analytical queries never compete with live traffic.
?
Interviewer follow-ups on architecture
  • “Why not just write directly to the database and read from it for the live chart?” — Be ready to explain why a relational DB doing read-your-writes with tens of thousands of concurrent small transactions will choke, and why decoupling ingest (stream) from serving (in-memory aggregate) is the standard fix.
  • “Why two separate connection types — HTTP for submitting a vote and WebSocket for receiving updates?” — Submitting a vote is a simple, idempotent, retryable request/response operation; receiving updates is a long-lived subscription. Conflating them into one channel couples very different reliability and scaling concerns.
  • “What happens if the WebSocket gateway tier restarts?” — Clients reconnect and resubscribe; because the gateway tier is stateless (state lives in Redis/Pub-Sub), no aggregate data is lost, only a brief reconnect blip per affected client.
04

Internal Working

The key internal insight is that the system never tries to update “the chart” directly from a vote. Instead it treats voting as a stream-processing problem: individual events flow through a pipeline that continuously folds them into a small, cheap-to-broadcast summary.

Idempotency and Exactly-Once Semantics

Each vote carries a deterministic idempotency key, typically hash(attendee_id, poll_id) (or including an option_id for multi-select polls where an attendee is allowed several distinct picks). Before a vote is admitted, the ingest service checks a fast idempotency store (Redis SETNX or a similar atomic “set if not exists”) keyed on that identifier. If the key already exists, the vote is treated as a duplicate/resubmission (common with flaky mobile networks retrying a request) and acknowledged without being counted twice. This single check is what prevents the most common correctness bug in these systems: a user’s spotty connection causing their client to retry a vote submission, which without idempotency would silently double their vote’s weight in the final tally.

Mergeable Aggregates (CRDTs and Approximate Counters)

Because multiple aggregation workers process different partitions of the same poll’s vote stream in parallel, the aggregate data structure must be safely combinable regardless of the order updates arrive in. This is the classic use case for CRDTs (Conflict-free Replicated Data Types) — specifically G-Counters (grow-only counters) or PN-Counters for vote tallies per option, which guarantee that merging partial counts from different workers, in any order, any number of times, produces the same correct total. For Q&A upvote ranking, a common technique is a probabilistic top-K structure (or simply per-question atomic counters combined with a periodic re-sort) since exact real-time global ranking of tens of thousands of questions is unnecessary — only the top ~20 questions the moderator will actually look at matter for the live view.

?
What the interviewer may ask
  • “Why not just use a single global counter with a mutex?” — At tens of thousands of votes per second across a fleet of workers, lock contention on a single counter becomes the bottleneck. Sharded counters (or CRDT-based merging) let increments happen in parallel and get combined cheaply.
  • “How do you avoid double-counting if two aggregation workers somehow process the same vote?” — CRDTs and idempotent, deterministic merge functions make re-application of the same event a no-op (associative, commutative, idempotent — the “ACI” properties), so at-least-once delivery from the broker doesn’t turn into over-counting downstream.

Push vs. Pull for Result Delivery

Attendee clients do not poll an HTTP endpoint every second to check for new results (that pattern collapses under tens of thousands of concurrent polling clients). Instead, clients hold a persistent WebSocket (or SSE) connection and are pushed a compact delta or snapshot whenever the aggregation layer decides it’s time to publish — typically on a fixed cadence (e.g., every 500ms–1s) rather than on every single vote, which smooths out the fan-out load and avoids “flickering” numbers on screen.

Topic-Based Subscription Model

Each active poll maps to its own logical broadcast topic (e.g. event:{event_id}:poll:{poll_id}). A client subscribes to the topic(s) relevant to whatever it’s currently viewing and unsubscribes when it navigates away or the poll closes, so gateway nodes never waste bandwidth pushing updates for content a given client isn’t looking at. This topic-per-poll granularity is also what makes multiple simultaneous polls within one event (common in breakout sessions) tractable — each has an entirely independent aggregate, partition set, and broadcast topic, with no shared mutable state between them.

Delta vs. Full Snapshot Broadcasts

Two broadcast strategies are used depending on payload size: for a small, fixed poll (e.g., four multiple-choice options), it’s simplest and cheapest to just broadcast the full current tally every interval — the payload is tiny and clients need no reconciliation logic. For larger result sets (a Q&A ranked list of dozens of questions, or a word cloud with hundreds of distinct terms), sending only the delta since the last broadcast (which items moved, new items that entered the top-K) keeps payload size roughly constant regardless of how large the underlying dataset grows, at the cost of slightly more complex client-side state reconciliation and the need for periodic full-snapshot resyncs to correct any drift from a missed delta.

05

Data Flow & Lifecycle

Here is the end-to-end journey of a single vote — from the tap on an attendee’s phone all the way to every subscribed screen refreshing the chart.

CLIENT API GW RATE-LIMIT VOTE INGEST BROKER AGG WORKER REDIS WS/PS POST /polls/{id}/vote (option, idempotency_key) check rate limit + auth token allowed forward vote SETNX idempotency publish VoteCast (partition by poll_id) 202 Accepted (ack, not final count) deliver (per-partition consumer) merge into CRDT update sharded aggregate loop every ~500ms: publish aggregate snapshot / delta publish to poll topic subscribers push updated chart data to all subscribed clients
Fig. 2 — Vote lifecycle. The client’s ack arrives on a fast path (dashed green) well before the aggregate broadcast (dashed green fan-out at the bottom) that reflects the vote on every screen.

Notice the client receives a fast acknowledgement (their vote was accepted) separately and much sooner than the aggregate update that reflects it on everyone’s chart. This separation is what allows the ingest path to stay low-latency (a few tens of milliseconds) while the fan-out path batches on a coarser cadence for efficiency — the two are deliberately decoupled.

Poll Lifecycle States

  1. Draft — created by presenter, not visible to attendees.
  2. Open — broadcast to attendees, ingest path accepts votes, aggregation workers actively publishing updates.
  3. Closed — ingest path rejects further votes (idempotently, so late/duplicate network retries are simply dropped); a final, exact tally is computed by reading the durable vote log directly (bypassing any approximate/streaming shortcuts) to guarantee the displayed final number is authoritative.
  4. Archived — results persisted for post-event reporting; hot data evicted from Redis to reclaim memory.
i
Subtle lifecycle detail

The live, in-flight aggregate shown while a poll is open is allowed to be eventually consistent (it may lag by a fraction of a second or momentarily miss a very recent vote), but the moment a poll is closed, the system switches to computing the final count from the durable, ordered vote log — trading a little latency for a hard consistency guarantee at exactly the moment people are looking closely at the number (“and the winner is…”).

06

Real-Time Aggregation Engine (Deep Dive)

This is usually the section that differentiates a strong system design answer from a mediocre one, because it’s where the actual “hard” engineering happens.

PARTITIONS Partition 1 votes hashed by poll_id Partition 2 bucketed by hash Partition 3 independent lane Partition N scales horizontally AGGREGATION WORKERS Worker 1 local shard counters Worker 2 CRDT G-counter merge Worker 3 idempotent apply Worker N stateless, replaceable CRDT MERGE associative / commutative / idempotent SNAPSHOT PUBLISHER periodic (~500ms) delta or full payload capped PUB/SUB fan-out tree
Fig. 3 — Sharded aggregation. Each partition feeds an independent worker; per-worker CRDT counters are merged (associative, commutative, idempotent) and periodically snapshot-published to the pub/sub fan-out tree.

Why Sharded, Approximate Aggregation Beats a Single Source of Truth Counter

A single row in a relational database with vote_count = vote_count + 1, hit by 10,000 concurrent requests, serializes on row-level locks and becomes the system’s bottleneck almost immediately — you’d be lucky to sustain a few hundred updates per second before latency spikes. The fix is the same pattern used by high-traffic counters everywhere (think “like” counts on major social platforms): split the counter into N independent shards (e.g., 16 or 64 per poll option), let writers increment a random shard, and sum the shards only when a reader needs the total. Writes become embarrassingly parallel; reads pay a small, bounded summation cost.

Windowed vs. Cumulative Aggregation

For simple vote counts, aggregation is cumulative — every vote ever cast for an option contributes to its running total for the poll’s lifetime. For “trending questions” or momentum-based ranking in Q&A (upvotes gained in the last 2 minutes matter more than upvotes from 20 minutes ago), a sliding time-window aggregation (e.g., an exponentially decayed score, or a fixed tumbling-window count) is more appropriate, similar to how trending-topic systems weight recency.

Backpressure Handling

When a poll opens to 50,000 attendees simultaneously, the ingest rate can spike far above steady-state. The message broker’s partitioned, durable log acts as a shock absorber — the ingest tier can accept and durably queue votes faster than the aggregation tier consumes them, and consumers catch up at their own sustainable pace without data loss. This is the classic producer/consumer decoupling benefit of a log-based broker over a direct RPC call from ingest straight into an in-memory aggregator.

?
What interviewer may ask
  • “Walk me through what happens if the aggregation worker fleet falls behind during a traffic spike — do attendees see stale numbers?” — Yes, briefly; the broker buffers durably, so nothing is lost, but the “real-time” chart may lag by a couple of extra seconds until workers catch up. This is an intentional, acceptable trade-off (availability and durability over instant freshness).
  • “How would you compute an exact final count if you were using approximate/probabilistic structures during the live phase?” — Recompute directly from the durable, append-only vote log at poll-close time, which is the authoritative source of truth; the streaming aggregate is only a fast approximation for the “live” experience.
  • “How many shards per counter, and how did you pick that number?” — Enough shards that contention per shard stays below the point where lock/CAS retries dominate latency at your peak QPS per poll option; too many shards makes summation slower and wastes memory. This is a tunable, workload-driven trade-off, not a fixed constant.

Word Clouds and Open-Text Aggregation

Word-cloud style polls (attendees type free-text answers, and the most common words/phrases render larger) add a normalization step ahead of aggregation: incoming text is lowercased, trimmed, and mapped through a light stemming/synonym pass (so “Cloud,” “cloud,” and “clouds” collapse into one bucket) before being fed into a sharded frequency counter identical in spirit to the multiple-choice vote counters. Because the space of possible answers is unbounded (unlike a fixed set of poll options), the aggregation layer typically tracks only an approximate top-N frequent terms using a space-saving/count-min-sketch style structure rather than an exact count for every distinct word ever submitted, which keeps memory bounded even if attendees submit thousands of unique responses.

Consistency Model, End to End

It’s worth being explicit about exactly which consistency guarantee applies at each stage, since “eventually consistent” is often used too loosely in interviews:

StageGuaranteeRationale
Vote acceptance (client ack)At-least-once durability once the broker accepts the publishThe client just needs to know its vote was received and will be counted; it doesn’t need to know the current total
Per-attendee vote uniquenessExactly-once, enforced via idempotency keyCorrectness-critical — double counting one person’s vote is a real bug, not just a UX nit
Live aggregate shown while poll is openEventually consistent, typically within one broadcast interval (e.g., ≤1s) of the true stateAcceptable staleness in exchange for throughput and simplicity; humans don’t notice sub-second lag on a moving chart
Final tally at poll closeStrongly consistent, computed directly from the durable, ordered vote logThis is the number people screenshot, quote, and act on — it must be exact and reproducible

This tiered approach — relaxed consistency where it’s cheap and unnoticed, strict consistency exactly where it’s visible and consequential — is a recurring theme in large-scale real-time systems and a strong signal in an interview that a candidate isn’t reaching for “just make everything strongly consistent” as a default.

07

Advantages, Disadvantages & Trade-offs

Every design choice above buys something and costs something. Naming both explicitly is what turns an interview answer from a laundry list into a defensible design.

Pros

  • Horizontally scales to tens of thousands of concurrent attendees and bursty vote spikes without touching a single hot row.
  • Ingest and fan-out are independently tunable — one bug or slowdown in aggregation cannot stop the system from accepting votes.
  • Durable vote log means nothing is ever lost and final tallies are exact and auditable.
  • Push-based fan-out over persistent connections is dramatically cheaper than 50k clients polling every second.

Cons

  • Considerably more moving parts than a “just write to Postgres” MVP — broker, cache, pub/sub, gateway all need operating.
  • Live aggregate is eventually consistent — explaining that to a nervous product manager “why isn’t the number exact right now?” is part of the job.
  • Millions of long-lived sockets are a very different beast operationally from stateless HTTP — connection storms, reconnect logic, kernel tuning all become first-class concerns.
  • Final-tally recompute from the log at poll close adds a small extra step at exactly the wrong moment (the presenter’s big reveal), so it has to be latency-budgeted for.
DecisionAdvantageTrade-off / Cost
Stream-based ingest + async aggregation (vs. direct DB writes)Handles massive write bursts without collapsing; horizontally scalableAdded architectural complexity; results are eventually consistent while a poll is open
Sharded/CRDT counters (vs. single locked counter)Near-linear write scalability, no lock contentionReading the “exact” total requires a summation step; slightly higher memory footprint
Push over persistent WebSocket (vs. client polling)Dramatically lower server load and lower latency at scaleRequires managing millions of long-lived connections and their failure modes; more complex infra than plain HTTP
Periodic snapshot broadcast (vs. broadcasting every single vote)Bounded, predictable fan-out bandwidth regardless of vote volumeIntroduces up to one broadcast-interval of latency (e.g., 500ms) on updates
Durable vote log as source of truth (vs. trusting the in-memory aggregate)Guarantees exact, auditable final results; enables replay and recoveryExtra storage cost and write path; final tally computation adds a bit of work at poll close
“Relaxed consistency while it’s cheap and unnoticed; strict consistency exactly where it’s visible and consequential.”
08

Performance & Scalability

At the target scale (tens of thousands of concurrent attendees, bursts of tens of thousands of votes within seconds), performance work concentrates on three specific bottlenecks.

50kconcurrent sockets
15–20k/speak ingest budget
~500msbroadcast cadence
N+2gateway redundancy

Connection Scale on the WebSocket Tier

A single modern server can typically hold on the order of tens of thousands to around a hundred thousand idle-ish WebSocket connections, depending on message frequency, heap/connection overhead, and OS tuning (file descriptor limits, ephemeral port ranges, kernel socket buffer tuning). For 50,000+ concurrent attendees, the gateway tier is horizontally scaled behind a load balancer that supports sticky/consistent routing, with connection state kept minimal (essentially just “which topics is this socket subscribed to”) so any gateway node is interchangeable and stateless from the app’s perspective.

Fan-out Cost Control

Broadcasting to 50,000 sockets from a naive single publisher is itself a scaling problem. The standard fix is a pub/sub fan-out tree: the aggregation layer publishes one message per poll update to a topic; a layer of broadcast/relay nodes subscribed to that topic each push to their own local shard of connected clients, turning an O(N) fan-out from a single node into a distributed, parallel fan-out across many gateway nodes, each handling only its own slice of connections.

Hot Partition / Hot Poll Problem

If a single, wildly popular poll dominates traffic (which is the common case — one poll open at a time to the whole audience), naive partitioning by poll_id alone concentrates all traffic on one partition. The fix is composite partitioning, e.g. hash(poll_id, attendee_id) % N, spreading a single poll’s vote stream across many partitions and many aggregation workers, then merging (which is safe and cheap thanks to the CRDT/shard-counter design above).

i
Capacity planning example

50,000 attendees, poll open for 10 seconds, 80% participation: 40,000 votes / 10s ≈ 4,000 votes/s average, but realistic bursts run 3–5× higher near the start of the window — provision (and load-test) for the order of 15,000–20,000 votes/sec sustained for a few seconds, not just the average.

Thundering Herd on Poll Open

The single sharpest spike in the entire system is the moment a presenter clicks “launch poll”: tens of thousands of clients receive a near-simultaneous WebSocket push telling them a new poll is available, and a large fraction submit a vote within the same one-to-two second window as their UI renders and they tap an answer. Two mitigations are standard: (1) the “poll opened” notification itself is fanned out through the same pub/sub tree used for results, so it scales the same way results do, and (2) client-side jitter — deliberately adding a few hundred milliseconds of random delay before the very first UI interaction becomes possible, or simply relying on natural human reaction-time variance — smooths what would otherwise be a single-instant spike into a slightly wider window, reducing peak instantaneous load on ingest.

Connection Scaling Math

If a single gateway node comfortably sustains 20,000 persistent connections with periodic (sub-second) broadcast traffic, 50,000 attendees requires at least 3 gateway nodes at capacity — but production sizing adds headroom for (a) uneven load balancing across nodes, (b) at least N+1 (ideally N+2) redundancy so a single node failure doesn’t cause a connection drop cascade onto already-full neighbors, and (c) reconnect storms, where a large fraction of clients reconnect within a short window after any gateway restart or network event — meaning real-world capacity planning for 50,000 attendees often provisions for 5–6 nodes’ worth of headroom, not the bare-minimum 3.

INGEST

Shed early, queue durably

Reject at the edge (rate limiter, idempotency check) before touching the broker; once accepted, the write is durable and can be replayed.

AGGREGATION

Parallelize by composite key

hash(poll_id, attendee_id) partitioning turns a hot poll into many cool partitions that CRDT-merge safely at the end.

FAN-OUT

Broadcast, don’t unicast

Publish one snapshot per interval to a topic; let each gateway push it to its own connected slice — O(N) fan-out becomes O(N/k) per node.

CLIENTS

Jitter and reconnect politely

Exponential backoff with jitter on reconnect prevents a naive stampede after any gateway blip.

CACHE

Keep the hot set small

Only currently-open polls live in Redis at full fidelity; closed polls demote to the durable log immediately, keeping memory bounded.

HEADROOM

Provision for the reconnect wave

Bare-minimum node count survives steady state; N+2 survives a real incident, including the reconnect storm that follows one.

?
What interviewer may ask
  • “Back-of-envelope: how many aggregation workers do you need?” — A good answer walks through peak votes/sec ÷ sustainable throughput per worker (accounting for CRDT merge cost and Redis round-trip time), then adds headroom for worker failure and rebalancing.
  • “What’s your bottleneck as attendee count grows from 50,000 to 500,000?” — Connection count on the gateway tier and fan-out bandwidth typically become the limiting factor before the aggregation/ingest path does, since votes/sec grows roughly linearly with attendees but so does the number of sockets needing pushed updates.
09

High Availability & Reliability

Events are scheduled and non-negotiable — going dark mid-keynote is a public incident. Reliability engineering here is not paranoia; it is table stakes.

  • Stateless gateways and ingest services: Any node can be killed and replaced without losing in-flight business state, since durable state lives in the broker, the vote log, and Redis — enabling rolling deploys and fast auto-healing.
  • Multi-AZ (and ideally multi-region) broker and cache clusters: The message broker and Redis cluster are deployed across availability zones with replication, so a single zone outage doesn’t halt an in-progress event.
  • Graceful degradation ladder: If the aggregation layer falls critically behind or Redis is unhealthy, the system degrades to showing “last known good” results with a subtle staleness indicator, rather than showing an error or blank chart — availability of a slightly-stale answer beats unavailability.
  • Client-side reconnect with exponential backoff and jitter: WebSocket clients automatically reconnect and resubscribe on drop, with backoff to avoid a reconnect storm overwhelming gateways right after a network blip or gateway restart.
  • Circuit breakers between tiers: The ingest service trips a circuit breaker to shed load (returning a fast, honest “try again” instead of votes queueing indefinitely) if the broker is unreachable, protecting upstream clients from cascading timeouts.
  • Idempotent replay for disaster recovery: Because the vote log is the source of truth and aggregation is a pure, deterministic function over it, the entire aggregate state for any poll can be rebuilt by replaying the log — a powerful recovery mechanism if the cache layer is lost entirely.
?
What interviewer may ask
  • “The event is live, on stage, in front of 50,000 people, and Redis just fell over. What happens?” — Walk through the degradation ladder: gateways keep serving the last cached snapshot, ingest keeps durably queuing votes to the broker (nothing is lost), and once Redis (or its replacement) is back, aggregation workers replay/catch up from the broker offset and resume publishing — attendees see a brief freeze, not an error, and no votes are lost.

Disaster Recovery Runbook, Conceptually

A well-designed system in this space treats “the aggregation cache is gone” as a routine, rehearsed scenario rather than a five-alarm emergency, precisely because the architecture was built with that recovery path in mind from day one:

  1. Detect via consumer lag and cache health alerts crossing threshold, or an outright node/cluster failure signal.
  2. Ingest continues accepting and durably queuing votes to the broker without interruption — this tier has no dependency on the cache being healthy.
  3. Gateways serve the last known-good snapshot to connected clients with a subtle “updating” indicator rather than an error state, preserving perceived availability.
  4. Once the cache layer is restored (new nodes, failover to a replica, or a fresh cluster), aggregation workers resume consuming from their last committed broker offset and rebuild the in-memory aggregate by replaying events forward — an operation bounded by however much backlog accumulated during the outage.
  5. Once aggregation workers report caught up (lag back near zero), the fan-out layer resumes normal-cadence broadcasting and the “updating” indicator clears.

The critical property enabling this whole runbook is that the aggregate is always a deterministic, replayable function of the durable log — nothing about recovery requires guessing or manual data reconciliation.

ADR-01 Accepted

Durable vote log is the single source of truth; in-memory aggregate is a derived cache

Context: Live aggregates in Redis are fast but volatile; a cache-only design has no principled recovery story after a node loss.

Decision: The append-only vote log (partitioned message broker + wide-column durable store) is authoritative. Redis holds only a derived projection that can be rebuilt at any time by replaying the log.

Consequence: Recovery from cache loss is a rehearsed, bounded operation rather than data loss. Poll-close final tallies always compute from the log, giving an exact answer even if the live aggregate drifted.

10

Security

Public, time-boxed, high-visibility events are an attractive target. Security here is layered defense across identity, ingest, transport, moderation, and the edge.

IDENTITY

Authentication & session binding

Every vote/question is tied to an authenticated attendee session (event registration token or short-lived JWT), preventing anonymous ballot stuffing.

CORRECTNESS

One-vote-per-attendee enforcement

Enforced server-side via the idempotency mechanism described earlier — never trust a client-side “already voted” flag alone.

ABUSE

Rate limiting & bot detection

Per-connection and per-IP rate limits, combined with behavioral heuristics (impossible vote velocity, identical timing patterns) to catch scripted vote manipulation.

MODERATION

Content moderation for Q&A

Automated profanity/toxicity filtering plus a human moderator queue before questions surface to the whole audience, since open text input at this scale is a predictable abuse vector.

TRANSPORT

TLS everywhere

TLS everywhere, including on the WebSocket upgrade; short-lived tokens rather than long-lived credentials on the socket.

ISOLATION

Tenant/event isolation

Strict authorization checks that an attendee’s vote can only affect the poll belonging to the event they’re actually registered for, preventing cross-event data leakage or manipulation in a multi-tenant SaaS deployment.

EDGE

DDoS protection at the edge

Since these events are public, high-visibility, and time-boxed, they’re an attractive DDoS target; edge scrubbing and aggressive anomaly-based throttling protect the origin before traffic even reaches application services.

Threat Model Summary

ThreatMitigation
Scripted/bot ballot stuffing (one identity voting repeatedly via automation)Server-side idempotency keyed on authenticated identity; velocity-based anomaly detection
Sybil attack (many fake registrations to gain many “legitimate” votes)Registration-level fraud checks tied into the event registration system, outside this system’s direct scope but a required upstream dependency
Offensive or harassing content submitted via open-text Q&A or word-cloud pollsAutomated toxicity/profanity filtering at ingest, plus a human moderation queue before wide visibility
Cross-event data leakage in a multi-tenant deploymentStrict per-event authorization checks on every read and write, enforced at the ingest and query layers, not just the UI
Connection-flood / DDoS against the WebSocket gateway during a high-visibility eventEdge-layer scrubbing, per-IP connection rate limits, autoscaled gateway capacity with pre-warming ahead of scheduled events
Replay of a captured vote request to inflate a countIdempotency key plus short-lived, single-use-adjacent tokens tied to the session

Q&A Moderation Pipeline

Open-text Q&A deserves its own mini-pipeline distinct from vote ingest, because it carries reputational risk that a wrong multiple-choice vote count simply doesn’t: a single offensive question broadcast to 50,000 attendees on a company’s public town hall is a real incident. A layered approach is standard: (1) a fast, automated classifier screens submissions synchronously and blocks the most clearly disallowed content before it’s even stored; (2) borderline content is queued for a human moderator, visible only to the moderator until approved; (3) approved content becomes visible to the full audience and enters the same upvote-ranking aggregation pipeline as any other question; (4) a “report” action lets attendees flag already-visible content for expedited moderator review, since no automated filter catches everything.

?
What interviewer may ask
  • “How do you stop someone from writing a script to vote 10,000 times?” — Layered defenses: authenticated, per-registered-attendee identity plus server-side idempotency (identity, not client state, defines “already voted”), rate limiting, and anomaly detection on vote velocity/timing — no single layer is sufficient alone.
  • “Why not just let all Q&A questions appear instantly, the way votes are counted instantly?” — Because open text carries qualitatively different risk than a bounded multiple-choice selection; the trade-off between “live and unmoderated” and “delayed but safe” is a product decision as much as a technical one, and most production systems land on a lightweight pre-screen plus post-hoc reporting rather than either extreme.
11

Monitoring, Logging & Metrics

You can’t operate a system like this from an intuition; you operate it from dashboards, traces, and calendar-aware alerts that know when an event is live.

  • Golden signals per tier: latency, traffic (votes/sec, active connections), error rate, and saturation (broker consumer lag, Redis memory/CPU, connection count vs. capacity) tracked separately for ingest, aggregation, and fan-out.
  • Consumer lag as the critical leading indicator: Message broker consumer lag for the aggregation workers is the single most important metric — rising lag is the earliest, clearest signal that live results are about to fall visibly behind reality.
  • End-to-end latency tracing: Distributed tracing (with a correlation ID attached at vote submission) to measure the full path from “vote accepted” to “reflected in a broadcast the client received,” not just the isolated latency of a single hop.
  • Real-user monitoring (RUM): Client-side timing of “time from tap to seeing the chart move” captured from actual attendee devices, since server-side metrics alone can miss client network/rendering delays.
  • Structured, sampled event logging: High-cardinality vote/question events are logged in a structured, sampled way to the analytics store, avoiding the cost of logging every single event at full fidelity to a system meant for debugging.
  • Alerting thresholds tied to event calendars: Since load is extremely spiky and tied to scheduled events, static always-on alert thresholds create noise; alerting is often tied to “is an event currently live” context to distinguish real incidents from expected idle-period quiet.

Example SLOs

MetricTarget
Vote submission acknowledgement latency (p99)< 200ms
Vote-to-broadcast end-to-end latency (p95)< 1.5s
Vote durability (accepted votes never lost)100%
WebSocket connection success rate> 99.9%
Aggregation consumer lag during peak< 3s, self-healing within 30s of a spike subsiding
12

Deployment & Cloud

Because traffic is spiky and event-scheduled, deployment strategy is inseparable from the event calendar.

  • Container orchestration (Kubernetes): Stateless services (ingest, gateway, aggregation workers) run as horizontally scalable Deployments with Horizontal Pod Autoscaling driven by custom metrics like votes/sec and connection count, not just CPU.
  • Pre-scaling / scheduled scale-up: Because major events are scheduled in advance, capacity is often proactively scaled up ahead of a known event start time rather than relying purely on reactive autoscaling, which can lag behind an instantaneous traffic cliff-edge at “poll opens.”
  • Managed streaming and cache services: Using a managed Kafka/Kinesis-equivalent and managed Redis/ElastiCache-equivalent reduces operational burden for the stateful backbone components that are hardest to run reliably in-house.
  • Multi-region deployment for global audiences: Regional gateway clusters close to attendee geography reduce WebSocket latency; the aggregation/streaming backbone can be regional-per-event (simplest) or globally replicated for events with a truly global, latency-sensitive audience.
  • Blue-green or canary deploys, never mid-event: Deployments of the hot-path services are scheduled around the event calendar — rolling out a change to the aggregation service five minutes before a 50,000-person keynote poll is a well-known way to cause an incident.

Cost Optimization

Traffic for this kind of system is extremely spiky and largely idle between scheduled events, which makes naive “always provisioned for peak” capacity wasteful. Common cost levers include: scaling stateless tiers (gateway, ingest, aggregation workers) down to a small baseline outside event hours and scaling up ahead of scheduled start times; using spot/preemptible capacity for aggregation workers where possible, since they’re stateless and can be replaced without data loss thanks to the durable log; and tiering storage so that hot, actively-queried data (the currently open poll) sits in memory while closed-event historical data moves to cheaper, colder storage after some retention window, keeping the expensive in-memory cache small and fast rather than growing unbounded across every event the platform has ever hosted.

Multi-Tenancy Considerations

A platform serving many customers’ events simultaneously (as opposed to a single internal deployment) needs the architecture above applied per-tenant with careful isolation: partition keys and cache keys are always namespaced by event_id (and often by customer/org ID above that), noisy-neighbor protection ensures one customer’s viral 100,000-attendee event doesn’t starve resource quota from a dozen smaller concurrent events on shared infrastructure, and rate limits and quotas are enforced per tenant rather than globally so abuse or a runaway bug in one customer’s integration can’t degrade the platform for everyone else.

i
Operational rule of thumb

Never deploy hot-path services within the pre-event freeze window (typically the 30–60 minutes before a major event starts). The blast radius of a bad rollout at exactly the wrong moment is orders of magnitude worse than the delay of shipping a change an hour later.

13

Databases, Caching & Load Balancing

Different data has different access patterns; using one store for everything is exactly the trap this design avoids.

DataStoreWhy
Live/hot aggregate per active pollRedis Cluster (sharded counters, sorted sets for Q&A ranking)Sub-millisecond atomic operations, native data structures fit the access pattern
Immutable, append-only vote/question eventsWide-column store (Cassandra/DynamoDB-style) or the broker’s own durable logWrite-optimized, horizontally scalable, natural fit for high-volume append-only data
Event/session/poll/user metadataRelational DB (PostgreSQL)Strong consistency and relational integrity for comparatively low-volume, structured data
Post-event analytics/reportingColumnar analytics store (ClickHouse/BigQuery-style)Optimized for large aggregate scans and reporting queries, isolated from the live path

Load balancing operates at two distinct layers: a standard L4/L7 load balancer distributes initial HTTP and WebSocket-upgrade requests across gateway nodes (often with consistent hashing so a reconnecting client tends to land near the same broadcast group, improving cache locality); and a separate, logical “topic-to-node” mapping in the pub/sub layer ensures broadcast fan-out work is spread evenly across gateway nodes rather than concentrated on whichever node happened to receive the aggregation service’s publish call.

14

APIs & Microservices

Two protocols, two very different jobs — and internal services drawn along the same fault lines they’ll fail along.

REST

REST/HTTP API

Poll/event CRUD, authentication, historical results export — standard resource-oriented endpoints suited to request/response semantics.

WEBSOCKET

WebSocket API

A thin subscribe/publish protocol (subscribe to a poll’s topic, receive snapshot/delta messages) deliberately kept minimal so the gateway tier stays simple, stateless, and easy to scale.

SERVICE FIT

Internal service boundaries

Ingest, aggregation, and fan-out are separate microservices specifically because they scale differently and fail independently — an aggregation-layer bug or slowdown should not be able to take down the ability to accept and durably queue new votes.

CONTRACT

Backpressure-aware contracts

Internal service calls between ingest and the broker are fire-and-forget publishes (not synchronous RPC waiting on aggregation), which is what allows the ingest tier’s latency to stay flat even when downstream aggregation is temporarily behind.

15

Design Patterns & Anti-patterns

Every real production system is a chosen set of patterns and an avoided set of anti-patterns. Name them explicitly.

Patterns Used

CQRS

Command Query Responsibility Segregation

The “write a vote” path and the “read the aggregate” path are entirely separate services with separate data models — a foundational pattern for this whole design.

EVENT SOURCING

Append-only log as truth

The durable vote log is the append-only source of truth; the aggregate is a derived, rebuildable projection.

PUB/SUB

Publish / Subscribe

Decouples the aggregation layer (publishers) from potentially thousands of gateway nodes (subscribers) delivering to clients.

SHARDING

Sharding / Partitioning

Applied at the counter level (shard counters) and the stream level (partitioned broker topics) to remove single-point contention.

RELIABILITY

Circuit breaker & bulkhead

Isolate failures in one tier (e.g., aggregation) from cascading into another (e.g., ingest).

CRDT

Conflict-free replicated types

G-Counter / PN-Counter for vote tallies and top-K structures for Q&A ranking give safe, order-independent merging.

Anti-patterns to Avoid

!
Synchronous read-your-write on every vote

Making the client wait for the vote to be fully aggregated and reflected before acknowledging couples ingest latency to aggregation latency and destroys throughput.

!
Client-side polling for results

Having 50,000 clients GET /results every second is functionally a self-inflicted DDoS.

!
Single global lock/counter for “simplicity”

Works great in a demo with 10 users, collapses immediately at real event scale — a classic case of premature optimization’s evil twin, premature under-engineering.

!
Trusting client-reported “already voted” state

Purely client-side vote-guarding is trivially bypassed and must always be backed by server-side enforcement.

!
One-size-fits-all consistency

Applying strict, synchronous consistency everywhere (including the live/open-poll display) sacrifices the performance headroom needed at scale, when only the poll-close moment truly needs it.

16

Best Practices & Common Mistakes

The design above is only as good as its operational habits. These are the disciplines that separate an event that runs boringly from one that trends on social media for the wrong reasons.

Do

  • Load test with realistic burst shape, not average load — steady-state average votes/sec dramatically understates the 5–10 second spike right after a poll opens; test the spike, not just the mean.
  • Pre-warm capacity ahead of known event start times rather than relying solely on reactive autoscaling that may lag the traffic cliff-edge.
  • Make the broadcast cadence configurable per event size — a 200-person webinar can broadcast every vote; a 50,000-person keynote should batch on a fixed interval.
  • Always compute the final, closing tally from the durable log, never from the approximate streaming aggregate, however close they usually are.
  • Design the client for graceful reconnection with backoff/jitter; a naive immediate-retry-on-disconnect client is a common cause of “thundering herd” reconnect storms after any gateway blip.

Common mistakes

  • Ignoring Q&A as “just a simpler poll.” Open-text Q&A introduces moderation, ranking, and abuse-resistance requirements that a multiple-choice poll doesn’t have; treating them identically under-serves both.
  • Forgetting per-tenant/per-event isolation in a multi-event SaaS platform, which can leak or cross-contaminate data between simultaneously running events sharing the same infrastructure.
  • Load-testing only the aggregation service in isolation instead of the full ingest → broker → aggregate → fan-out path with realistic connection counts.
  • Optimizing purely for the “happy path” and skipping the reconnect / cache-loss / broker-lag scenarios — those are exactly when the system will be judged.
17

Real-World Industry Examples

No two production systems in this space are built identically, but the recurring architectural DNA — decoupled ingest, sharded/mergeable aggregation, and push-based fan-out over persistent connections — shows up consistently across the industry, because it’s a direct consequence of the same underlying constraints (high fan-in, high fan-out, tight latency budget) rather than any single company’s specific implementation choice.

POLLING SAAS

Slido / Mentimeter

Purpose-built live polling and Q&A platforms embedded into webinar tools and conferencing software; their core engineering challenge is exactly this design — fast fan-in of votes, real-time aggregate fan-out to a shared screen and thousands of individual devices, and Q&A moderation at conference scale.

VIDEO CONF

Zoom / Microsoft Teams

Large video conferencing platforms bolted real-time polling onto existing massive-scale, low-latency infrastructure originally built for audio/video, reusing their existing signaling and pub/sub backbones for poll result delivery to avoid building a second parallel real-time system from scratch.

LIVE STREAM

Twitch / YouTube Live

Live streaming platforms handle a structurally similar problem (extremely high fan-in chat messages, extremely high fan-out to millions of viewers) and popularized many of the sharded pub/sub and CRDT-style counter techniques described here, at even larger scale.

QUIZ

Kahoot!

A live quiz platform whose core technical challenge — collecting simultaneous answers from tens of thousands of participants (often students) within a tight countdown window and instantly showing a leaderboard — is essentially the same ingest/aggregate/fan-out pipeline, with the added wrinkle of scoring based on answer speed.

SOCIAL

Reddit-style vote systems

The sharded-counter and eventually-consistent-then-reconciled approach to vote tallies at massive scale (popularized publicly in various engineering blog posts across the industry) is the same underlying pattern applied here to live event polling.

i
Trust but verify

None of these companies invented these patterns in isolation. What they did do is invest heavily in load testing, calendar-aware operations, and honest post-incident writeups — which is how the same handful of patterns became the industry default.

18

FAQ

The questions candidates and product managers actually ask, answered honestly.

Q1

Why not just use a simpler request/response model where the client fetches the latest results every second?

At tens of thousands of concurrent attendees, that turns into tens of thousands of requests per second just for polling, most of which return “nothing changed,” wasting enormous server and network capacity compared to a push-based model where updates are sent only when there’s something new to send.

Q2

Is eventual consistency really acceptable for something as visible as a live vote count?

Yes, during the “live” phase — humans perceive a chart updating within a second as “real time” even if it’s technically a fraction of a second behind reality, and the system switches to a strongly consistent, exact computation at the moment the poll closes and the final number actually matters.

Q3

How do you handle an attendee who joins the event late, after a poll has already been open for a minute?

On WebSocket connect/subscribe, the gateway (or aggregation service) sends a full current snapshot of the poll’s aggregate state before switching the client to incremental delta updates, so late joiners see accurate current results immediately rather than starting from zero.

Q4

What happens to votes cast in the last second before a poll is force-closed by the presenter?

The close operation is applied at a specific logical timestamp/offset in the durable vote log; votes with a server-received timestamp after that cutoff are excluded from the final tally, and the ingest service can immediately start rejecting further votes for that poll with a clear “poll closed” response.

Q5

Could this be built entirely serverless (e.g., managed functions plus managed pub/sub)?

Yes for many of the stateless tiers (ingest validation, snapshot publishing), which is a common and cost-effective choice for the highly bursty, idle-most-of-the-time traffic pattern of scheduled events; the stateful backbone (broker, cache cluster) typically still runs as a managed clustered service rather than as functions, since it needs persistent, low-latency state.

Q6

How is Q&A upvote ranking different from vote counting, from a systems perspective?

Vote counting only needs a small, fixed set of totals (one per poll option). Q&A ranking needs an ordered top-K over a potentially large and growing set of distinct questions, which is a heavier data structure (sorted set / priority structure) than a flat counter, and typically only needs to be exactly correct for the handful of items actually visible on screen rather than the entire long tail.

Q7

What if the presenter’s own connection drops during a live poll?

The presenter console is just another WebSocket subscriber to the same broadcast topic as attendees, so on reconnect it receives a fresh snapshot exactly like any other client — the poll itself keeps running and collecting votes on the server side regardless of whether any particular viewer, including the presenter, is currently connected.

Q8

How would you extend this design to support breakout-room-level or region-level segmented results, not just one global tally?

Extend the partition/shard key to include the segment (e.g., hash(poll_id, segment_id, attendee_id)) and maintain a separate CRDT aggregate per segment in addition to the global one; the pub/sub topic model extends naturally by adding a per-segment topic alongside the event-wide topic, so clients subscribe to whichever slice is relevant to them.

Q9

Is a message broker like Kafka strictly necessary, or could a simpler in-memory queue work?

For a small internal tool with modest scale and no durability requirement, an in-memory queue is a reasonable simplification. At the scale this design targets, the durable, replicated, partitioned log is what provides both shock-absorbing capacity for traffic bursts and a replayable source of truth for recovery and exact final tallies — properties a plain in-memory queue doesn’t give you, since a crashed process would simply lose whatever was queued.

19

Summary & Key Takeaways

The whole design is a composition of well-known patterns matched carefully to the specific shape of the workload — a bursty, high-fan-in write spike followed immediately by a sustained, high-fan-out read pattern to the same population.

THREE-PART SPLIT

Ingest / Aggregate / Fan-out

Treat the problem as three distinct sub-problems with different scaling shapes and design each independently.

DECOUPLE

Broker as shock absorber

Decouple vote acceptance from result computation using a partitioned, durable message broker; this is what lets the system absorb massive, bursty write spikes without data loss.

MERGE SAFELY

Sharded, mergeable counters

Use sharded, mergeable counters (CRDTs) to make aggregation embarrassingly parallel and safe under at-least-once delivery.

PUSH

Push, don’t pull

Persistent connections and periodic broadcast snapshots keep fan-out cost bounded regardless of vote volume.

Key takeaways

  • Three sub-problems, three scaling shapes. Ingest (fan-in), aggregate (stream processing), and fan-out (broadcast) each deserve their own design and their own load test.
  • The broker is the shock absorber. A durable, partitioned log lets ingest stay flat while aggregation catches up, and lets any downstream tier be rebuilt from replay.
  • Sharded / CRDT counters, not a single global lock. Contention-free writes, cheap bounded reads, safe merging under any delivery order.
  • Push over persistent connections. Polling by 50,000 clients is a self-inflicted DDoS; a broadcast topic push per interval is cheap and predictable.
  • Consistency is tiered on purpose. Eventually consistent while the poll is live; strongly consistent (recomputed from the log) the instant it closes.
  • Reliability comes from statelessness at edges, durability at the core. Every tier degrades gracefully; the durable vote log guarantees nothing is ever silently lost.
  • Security is first-class. One-vote-per-identity, rate limiting, and moderation are architectural concerns, not bolt-ons.
  • Operate around the event calendar. Pre-warm capacity, freeze deploys, alert only when an event is actually live — treat time itself as an input to the system.

Pulled together, none of these individual pieces — a message broker, a sharded counter, a pub/sub fan-out tree — is exotic on its own. What makes this a genuinely interesting system design problem is composing them correctly around the specific shape of the workload: a short, extremely bursty write spike from tens of thousands of independent sources, immediately followed by a sustained, high-fan-out read pattern to the same population, all under a latency budget tight enough that the result still feels “live” to a room full of people watching a shared screen.

i
Interview takeaway

Strong candidates don’t reach for “just make everything strongly consistent” and don’t reach for “just cache it in Redis” either. They match the tool to the sub-problem — broker for ingest, sharded counter for aggregation, pub/sub for fan-out — and are honest about which stage is eventually consistent and which stage is exact.

Leave a Reply

Your email address will not be published. Required fields are marked *