Learning pathsPricingChecking sign-in…
Question Breakdowns

Event Voting

Accept one eligible vote per person in an event and publish counts without counting request retries twice.

Accept one eligible vote per person in an event and publish counts without counting request retries twice. This worked design targets SDE-1; the scale figures below are exercise assumptions, not claims about a named company's implementation.

Clarify the prompt

Ask the interviewerDecision to record
Can voters change their choice?Updates require a version and count compensation.
What establishes voter eligibility?A browser cookie is weaker than an authenticated event roster.
Are counts live or final-only, and when does voting close?Define the authoritative close boundary.

Requirements and scope

The core deliverable is: Accept one eligible vote per person in an event and publish counts without counting request retries twice. The first design implements the APIs below and the state constraints in the data model. Agree the latency, freshness, retention and failure policy before adding infrastructure. Every successful acknowledgment must have an explicit meaning: accepted intent, committed state or completed external work. These are separate milestones.

Capacity and bottleneck reasoning

Assume 100,000 eligible voters over ten minutes: about 167 votes/s average, with 2,000/s bursts. At 200 bytes/vote the raw ledger is 20 MB. One counter row per candidate can become hotter than the vote table.

Keep average and peak demand separate. Add replication, indexes, retries and safety headroom to raw arithmetic before selecting capacity. Load-test the busiest ownership key and recovery traffic as well as uniform happy-path throughput.

APIs and data model

  • PUT /events/{id}/vote {optionId,version,requestKey} -> accepted or 409
  • GET /events/{id}/results -> count plus freshness marker
  • POST /events/{id}/close -> immutable finalization intent
Contract / pseudocode
event(event_id PK, closes_at, status)
eligibility(event_id, voter_id, PRIMARY KEY(event_id,voter_id))
vote(event_id, voter_id, option_id, version, PRIMARY KEY(event_id,voter_id))

Authorize resource ownership at the API boundary; do not trust IDs or state supplied by a client. Bind retry identity to the caller and payload for mutating operations. Reject an incompatible replay instead of returning an unrelated stored result.

Component relationships

Event Voting: directed relationships and authoritative boundaries
Event Voting: state boundaries
Read the diagram

Follow the labeled relationship panels; they are separate directed paths, not one global timeline.

Open original
Event Voting: state boundaries
Event Voting: directed relationships and authoritative boundaries

Event Voting: state boundaries

Step-by-step design

1. Establish the authority

Authenticate and verify eligibility in the event boundary. Use the unique event/voter key to enforce one current vote; reject invalid options and requests after the chosen close instant.

2. Make retries and concurrent work safe

For immutable votes, insert once and return the prior outcome on identical retries. If vote changes are permitted, compare the version and record an audit event in the same transaction.

3. Scale and recover the critical path

Derive final results from authoritative votes. Live counts can be an asynchronous projection with deduplicated events; display freshness and rebuild it rather than trusting a mutable cache as the final tally.

Worked request and concurrency example

Voter V submits option A and loses the response. The retry cannot create a second row because (event,V) is unique. A counter consumer may receive the same vote event twice; it records processed event identity with the projection update. At close, freeze the accepted-vote set and calculate final counts from that set. If changing votes is allowed, A-to-B is a new version that subtracts from A and adds to B exactly once in the projection.

Failure and recovery walkthrough

A live counter misses events during an outage. Keep accepting durable votes if capacity allows, mark the displayed tally stale and replay or rebuild. A close request racing with a vote needs one database-time rule, not inconsistent clocks on API servers.

Event Voting: one concrete failure or state-transition sequence
Event Voting: concrete trace
Read the diagram

This numbered trace follows one scenario; explain the durable evidence at each transition.

Open original
Event Voting: concrete trace
Event Voting: one concrete failure or state-transition sequence

Event Voting: concrete trace

Deep dive: one accepted ballot and one defensible closing boundary

This original exercise is a small event poll, not a public-election security or anonymity protocol. An authenticated event roster defines eligibility. Choose immutable ballots as the first version; optional vote changes are versioned replacements. A unique event/voter row prevents duplicate accepted ballots for that identity, but does not prove that one human cannot acquire multiple eligible identities. State the identity/threat assumptions before claiming fairness.

Decide what close means

Two valid product choices differ: acceptance before a published database-time deadline, or acceptance before an explicit serialized close transition. Do not mix an API node's clock, client send time and a background worker's execution time. This exercise uses both an open event state and decision_time < closes_at under one event authority. At equality, the vote is rejected.

Acquire the event guard, then read the authoritative time and validate state, eligibility and option membership; commit the vote, receipt and publication intent in the same transaction. Every vote, roster mutation affecting current eligibility, option mutation and close path must obey the chosen protocol. Receipt lookup for an already accepted identical operation can return its committed outcome after close; it does not admit a new vote.

Contract / pseudocode
vote_request(event_id, voter_id, request_key, fingerprint, accepted_version)
vote(event_id, voter_id, option_id, version)
vote_change(event_id, sequence, voter_id, prior_option, next_option)
event(event_id, status, closes_at, accepted_sequence, final_sequence)
UNIQUE(event_id, voter_id)
UNIQUE(event_id, voter_id, request_key)

The simplest event lock serializes admissions and close, making the boundary easy to explain. It is not free scalability. If a guarded transaction takes 5 ms, the ideal serialized ceiling is around 200/s before overhead, insufficient for the example 2,000/s burst. Shorter transactions, bounded admission or a deliberate shared-writer/exclusive-closer protocol can improve throughput. A sharded design needs a coordinated barrier/checkpoint across shards; a cached open=true flag is not that barrier. Explicitly choose a design that meets the measured burst and closing objective.

Trace the two race orders

If a vote obtains the guard and validates before the deadline, it may commit and then close obtains the guard; the accepted sequence is included. If close obtains the guard first, it freezes the final accepted sequence and a later new vote is rejected. A request sent before the deadline but delayed in the network is not automatically accepted. A transaction must use a clock value evaluated at the decision after lock acquisition, not a start timestamp from before a long lock wait.

Voting close recovery: voter V commits ballot sequence 42; the response is lost; close freezes accepted sequence 42; V retries the original request key; receipt returns sequence 42 without another vote
Returning an accepted receipt after close is different from accepting a late ballot.
Read the diagram

V commits ballot at sequence 42 → The accepted response is lost → Close freezes accepted sequence 42 → V retries the original scoped request key → Return receipt 42 without another ballot

Open original
Event Voting: recovery decision
Voting close recovery: voter V commits ballot sequence 42; the response is lost; close freezes accepted sequence 42; V retries the original request key; receipt returns sequence 42 without another vote

Returning an accepted receipt after close is different from accepting a late ballot.

Freeze the accepted set before final tallying. The final result is a deterministic aggregation of authoritative votes at that boundary. A scheduled close worker that runs late must not extend a fixed deadline. Administrative early close follows the same authority and leaves an audited reason. Reopening is a new explicit event generation/policy, not silently deleting finalization evidence.

Optional changes need ordered replacement semantics

For mutable ballots, require an expected version and a new scoped operation key. A-to-B commits version 2 and an event containing the prior and new option in the same transaction. Two edits based on version 1 cannot both commit. Identical retries return version 2, including after close, while an incompatible payload conflicts. The live-count projection cannot blindly apply “minus A, plus B” for every delivery.

One simple projection consumes a complete per-event ordered stream, storing its last applied sequence atomically with counts. Duplicate sequence is ignored; a gap pauses/replays rather than skipping. Alternatively store each voter's latest applied version and coordinate replacements with the prior projected contribution. Version 2 arriving before version 1 requires an explicit gap or absolute-state protocol; event-ID deduplication alone does not solve ordering. Recovery can rebuild a fresh projection from authoritative ballots at a frozen checkpoint, validate totals and atomically switch readers.

Results, privacy and overload are product contracts

Live results show an applied sequence/freshness marker and may lag. Finalization checks that tally totals equal the eligible accepted-ballot set under the frozen policy; only eligible candidate IDs appear. Do not silently remove previously accepted ballots because today's roster changed. Decide whether eligibility is snapshotted, checked at acceptance or subject to explicit audited disqualification. This baseline checks at acceptance and preserves accepted evidence.

For 100,000 voters over ten minutes, average is about 167/s; a 2,000/s burst drives the authority design. If safe admission is 500/s for ten seconds while arrivals are 2,000/s, backlog grows by 15,000 requests. Queuing them is not a promise they will pass the deadline. Return overload/retry guidance with a visible cutoff rather than implying every pre-close click is accepted.

Restrict individual ballots and administrative actions separately from aggregate results. Never describe identity-linked rows as cryptographically anonymous. Avoid voter IDs and choices in general logs; use bounded metrics for acceptance/rejection reasons, lock wait, oldest projection gap and close-to-final latency. Test roster/option changes as well as duplicate submissions.

Voting assessment: prove the set being counted

Question: V's accepted response is lost and the identical request is retried after close. Must it be rejected as a new late vote?

Show answer and explanation

Answer: Resolve the receipt first. Return its accepted identity/version without another mutation. A new or incompatible operation must still satisfy the closing rule.

Question: A projection deduplicates event IDs. Is applying version 2 before version 1 automatically correct for replacement deltas?

Show answer and explanation

Answer: No. Deduplication does not establish order or the correct prior contribution. Use an ordered/gap-aware protocol or versioned absolute contribution and reconcile against the authoritative set.

Question: Is the final live-cache value sufficient to certify the result?

Show answer and explanation

Answer: No. Freeze the accepted boundary and aggregate/reconcile authoritative ballots. A stale or incomplete projection must not become final merely because the close button was clicked.

Score one point for eligibility/identity assumptions, atomic acceptance and exact cutoff, receipt/version semantics, ordered projection recovery and final-set/privacy verification. Review Transactions and Isolation before defending a more concurrent event guard.

Decisions and alternatives

DecisionTrade-off
Alternative 1Synchronous counters offer fresh totals but contend on popular options. An event projection scales reads while making freshness explicit.
Alternative 2Anonymous ballots and strong duplicate prevention require an eligibility/token design that preserves the promised privacy; do not store unnecessary identity-to-choice links.

Operational contract

Alert on uniqueness violations, ineligible attempts, projection lag and differences between rebuilt and live totals. Keep administrative close and correction actions auditable. Restrict access to individual ballots.

Choose alert thresholds from the agreed user-facing objective. Assign an owner for stuck work and a replay/repair procedure that preserves the original business identity. Bound queue depth, concurrency and recovery throughput so an outage does not produce an uncontrolled catch-up burst.

Foundation, senior and staff depth

  • Foundation: Define eligible identities, immutable ballots and the exact cutoff.
  • Senior: Resolve accepted retries after close and order replacement projections.
  • Staff: Defend event authority throughput, frozen final tallies and explicit privacy assumptions.

Practice before revealing the answer

Spend 45 minutes: 5 clarifying scope, 7 on capacity/APIs/state, 12 on the first architecture, 15 on the concurrency and failure examples, and 6 on trade-offs and operations. Draw your own design before comparing it with this chapter.

Question: Can live displayed totals be used as the final election result?

Show answer and explanation

Answer: Only after reconciling them against the authoritative accepted votes and the closing boundary. Projection delivery may lag or duplicate; the vote ledger and eligibility rules determine the result.

Verification and rollout

Enumerate vote/close orderings and reject eligibility at cutoff equality. Replay accepted keys after close, reject changed payloads and stale versions, deliver projections with duplicates and gaps, then reconcile the final authoritative set.

Primary technical reference

Official documentation for the mechanism used in this exercise. This lesson is an original interview design, not a description of undisclosed company internals.

Before this design, review these mechanisms and identify which owns the critical state transition:

Use the Interview Playbook to adapt the depth and timing, then return to the question breakdown overview.

Your study notes