Core Entities
Poll: poll_id, creator_id, question, poll_type (SINGLE_CHOICE, MULTIPLE_CHOICE, RANKED, RATING), status (DRAFT, ACTIVE, CLOSED), is_anonymous, allow_change_vote, visibility (PUBLIC, PRIVATE, ORGANIZATION), start_time, end_time, settings (JSONB: max_choices, require_authentication, show_results_before_close), created_at. PollOption: option_id, poll_id, text, image_url, display_order. Vote: vote_id, poll_id, voter_id (NULL if anonymous), voter_fingerprint (browser fingerprint hash), selected_options (int array for multi-choice), rank_order (JSONB for ranked choice), created_at, ip_address, user_agent. VoteResult: poll_id, option_id, vote_count, percentage, last_updated. (Materialized aggregate, updated on each vote.) PollAnalytics: poll_id, total_votes, unique_voters, completion_rate, avg_response_time_seconds, geographic_distribution (JSONB), device_breakdown (JSONB), time_series (JSONB: votes per hour).
Vote Submission with Deduplication
class VotingService:
def cast_vote(self, poll_id: int, voter_id: Optional[int],
options: list[int], request_meta: dict) -> VoteResult:
poll = self.db.get_poll(poll_id)
# Validate poll is active
if poll.status != "ACTIVE":
raise PollNotActive(poll_id)
if poll.end_time and datetime.utcnow() > poll.end_time:
raise PollExpired(poll_id)
# Validate options
valid_option_ids = {o.option_id for o in poll.options}
if not all(o in valid_option_ids for o in options):
raise InvalidOption()
if poll.poll_type == "SINGLE_CHOICE" and len(options) != 1:
raise InvalidSelection("Single choice poll requires exactly 1 option")
# Deduplication key
if voter_id:
dedup_key = f"voted:{poll_id}:{voter_id}"
else:
# Anonymous: use IP + user agent fingerprint
fingerprint = self._compute_fingerprint(request_meta)
dedup_key = f"voted:{poll_id}:{fingerprint}"
with db.transaction():
# Check for existing vote (atomic with Redis for speed,
# DB constraint as fallback)
if self.redis.get(dedup_key):
if poll.allow_change_vote:
return self._change_vote(poll_id, voter_id, options)
raise AlreadyVoted()
# Insert vote record
vote = self.db.insert("votes", {
"poll_id": poll_id, "voter_id": voter_id,
"selected_options": options,
"ip_address": request_meta["ip"],
"user_agent": request_meta["user_agent"],
"voter_fingerprint": fingerprint if not voter_id else None
})
# Update materialized vote counts (in same transaction)
for option_id in options:
self.db.execute(
"UPDATE vote_results SET vote_count = vote_count + 1 "
"WHERE poll_id = %s AND option_id = %s",
poll_id, option_id
)
self.db.execute(
"UPDATE vote_results SET percentage = "
"vote_count * 100.0 / (SELECT SUM(vote_count) FROM vote_results "
"WHERE poll_id = %s) WHERE poll_id = %s",
poll_id, poll_id
)
# Set dedup key in Redis (TTL = poll end time + 24h for safety)
self.redis.setex(dedup_key, 86400 * 30, "1")
# Publish real-time update
self.pubsub.publish(f"poll:{poll_id}:update", vote.to_json())
return self.db.get_vote_results(poll_id)
Real-Time Results with WebSocket
Live results update as votes come in. Architecture: on each vote, publish a PollUpdateEvent to Redis pub/sub channel (poll:{poll_id}:update). A WebSocket gateway subscribes to these channels and pushes updates to all connected clients watching that poll. Throttling: if a poll receives thousands of votes per second (viral poll), push at most one update per 500ms per connected client (debounce). Client receives: {option_id: count, …} partial update or full recalculation. Rate limiting updates: the pub/sub consumer tracks last-push timestamp per poll. If < 500ms since last push: buffer the update and schedule a delayed push. This prevents WebSocket message floods to thousands of clients on viral polls.
Fraud Detection and Vote Integrity
Fraud vectors: (1) Same user voting multiple times with different accounts (ballot stuffing). Detection: IP rate limiting (max N votes per IP per poll per hour), browser fingerprinting (canvas, WebGL, fonts), device ID tracking. (2) Bot voting: automated script votes. Detection: CAPTCHA on anonymous polls, rate limiting by IP, behavioral analysis (too-fast click pattern). (3) Vote buying: users selling votes. Mitigation: real-time anomaly detection on vote velocity per IP subnet. Implementation: track votes per IP in Redis sorted sets with sliding time windows. INCR vote_count:{poll_id}:{ip_prefix} with TTL. Threshold alerts: if any IP prefix votes > 50 times in 10 minutes: flag for manual review, require CAPTCHA for subsequent votes from that IP, and mark those votes as SUSPICIOUS in the database. Do not automatically delete suspicious votes — human review determines validity.
See also: Twitter/X Interview Prep
See also: Snap Interview Prep
See also: LinkedIn Interview Prep