SwiftPolls: Real-time Audience Engagement
SwiftPolls is a modern, full-stack web application designed to transform audience engagement through real-time interactive sessions and asynchronous surveys. Built with a robust TypeScript stack, SwiftPolls bridges the gap between live presentation tools (like Kahoot or Mentimeter) and traditional form builders (like Google Forms), offering a unified platform for creators, educators, and teams to gather instant feedback and actionable insights.
This case study explores the product design, technical architecture, and engineering solutions behind SwiftPolls, specifically focusing on how the platform handles high-concurrency real-time voting and seamless state synchronization.
1. The Problem Context
In today's hybrid work and learning environments, keeping an audience engaged is notoriously difficult. Presenters often struggle with:
- Tool Fragmentation: Using one platform for live, interactive meeting polls and a completely different tool for post-meeting asynchronous feedback.
- Friction in Participation: Forcing users to create accounts or download apps just to answer a single question severely drops participation rates.
- Performance Bottlenecks: "Thundering herd" scenarios where hundreds or thousands of participants submit votes at the exact same second can easily overwhelm a traditional relational database, causing latency or crashes.
2. The SwiftPolls Solution
SwiftPolls was engineered to solve these exact pain points by offering two distinct modes of operation under one roof:
- Live Mode: Presenters generate a unique 6-character room code. The audience joins instantly via their mobile or desktop browsers without needing an account. The presenter controls the pace, pushing questions to audience screens and revealing real-time charts as votes stream in.
- Async Mode: Creators build a survey, set an optional expiration date, and share a public link. Respondents can answer at their own pace, and the system automatically closes the poll when the deadline hits.
Key Product Features
- Intuitive Poll Builder: A drag-and-drop interface (powered by React Hook Form and Zod) to construct multi-question polls with custom settings (e.g., mandatory questions, live result visibility, time limits).
- Presenter Dashboard & Controls: A dedicated interface for hosts to manage active sessions, toggle answer locks, push next questions, and end sessions.
- Rich Analytics: Beautiful, interactive dashboards utilizing Recharts to display bar charts, pie charts, and response trends.
- Frictionless Audience Experience: A mobile-optimized, real-time voting interface that updates instantly without page reloads.
3. Technical Architecture & Stack
SwiftPolls employs a modern, type-safe stack across both the client and the server, ensuring developer velocity and application reliability.
The Frontend (Client)
- Framework: React 19 built with Vite for lightning-fast HMR and optimized builds.
- Language: TypeScript.
- State Management & Data Fetching: TanStack Query (React Query) v5 handles server state, caching, and optimistic updates.
- Real-time: socket.io-client for persistent WebSocket connections.
- Styling: Tailwind CSS v4 for utility-first, responsive design, combined with lucide-react for iconography.
- Forms & Validation: react-hook-form paired with zod for strict client-side validation that mirrors backend schemas.
The Backend (Server)
- Runtime: Node.js with Express 5.
- Database: PostgreSQL, providing strong relational guarantees for users, polls, and response data.
- ORM: Drizzle ORM, offering a lightweight, hyper-performant, and type-safe database layer.
- In-Memory Store: Redis (via ioredis), utilized for caching, session management, and rate limiting.
- Background Jobs: BullMQ, backed by Redis, to handle intensive background tasks and scheduled events.
- Real-time Engine: Socket.IO server, deeply integrated with Express to broadcast state changes.
- Authentication: A dual-strategy approach using JWT (Access/Refresh tokens) and Passport.js for Google OAuth 2.0.
4. Engineering Challenges & Solutions
Building a platform that must instantly reflect state across multiple clients while safely recording data at scale presented several distinct architectural challenges.
Challenge A: The "Thundering Herd" Voting Problem
In a live session, a presenter might ask a question to an audience of 1,000 people. Human behavior dictates that a vast majority of those 1,000 people will tap their screens within a tight 3-to-5-second window.
If the backend executed a standard SQL UPDATE analytics SET count = count + 1 or INSERT for every single incoming vote, the PostgreSQL database would face severe row-level lock contention and connection pool exhaustion, leading to massive latency spikes.
The Solution: Redis Buffering & BullMQ Flush Workers
Instead of writing directly to Postgres, SwiftPolls utilizes Redis as a high-throughput, in-memory buffer.
- Atomic Increments: When a vote hits the
/api/responsesendpoint, the backend uses Redis's highly optimized, atomic INCR command:redis.incr('analytics:${poll_id}:${option_id}'). This operation takes microseconds and never blocks the database. - Real-time Broadcast: The backend immediately broadcasts the updated counts to the specific Socket.IO room, ensuring the presenter's screen shows the bar charts climbing instantly.
- The Analytics Worker: To ensure data permanence without hurting performance, SwiftPolls employs a BullMQ repeatable job named
analytics-snapshot. Every 10 seconds, this worker wakes up:- It scans Redis for all
analytics:*keys using a pipeline. - It retrieves the current buffered counts and atomically decrements them (using
DECRBY). - It takes this aggregated batch of votes and performs a bulk
UPSERTtransaction into PostgreSQL.
- It scans Redis for all
Result: The PostgreSQL database only receives a handful of optimized queries every 10 seconds, regardless of whether there are 10 or 10,000 concurrent voters.
Challenge B: Synchronizing Live Session State
In live mode, the audience's screens must precisely mirror the presenter's intent. If the presenter moves to Question 2, all audience devices must instantly render Question 2. If the presenter locks voting, all audience buttons must instantly disable.
The Solution: Socket.IO Rooms and Event-Driven State
SwiftPolls leverages Socket.IO's "Rooms" feature to segment traffic.
- When a session starts, a unique
room_codeis generated. - Audience members join, emitting a
join_roomevent. The backend places their socket connection into that specific room. - The Presenter acts as the authoritative source of truth. When they click "Next Question", the client fires an API call and emits a
next_questionWebSocket event. - The backend updates the session's
current_question_indexin PostgreSQL (for persistence if someone reconnects) and immediately broadcastsio.to(room_code).emit("question_changed", payload). - The React client listens for these events via a custom
SocketContextand updates local state, triggering instant UI re-renders for the audience.
Challenge C: Precision Scheduling for Async Polls
Async polls allow creators to set an expiration date (e.g., "Close this survey on Friday at 5:00 PM"). The system must accurately close the poll at that exact millisecond and optionally notify the creator, without running expensive, continuous database polling (like a cron checking the DB every minute).
The Solution: BullMQ Delayed Jobs
When a creator publishes an async poll with an expires_at timestamp, the backend calculates the exact millisecond delay: delay = expiresAt.getTime() - Date.now().
It then adds a job to the BullMQ poll-expiry queue with that specific delay. Redis holds this job efficiently in memory. When the precise moment arrives, the BullMQ worker wakes up, processes the job, sets is_active: false in the database, pushes a WebSocket event to boot any active respondents, and uses Nodemailer to send a "Poll Expired" summary email to the creator.
5. Security & Data Integrity
Handling user data and preventing abuse is critical for a polling platform.
- Rate Limiting: To prevent malicious users from spamming votes, the
express-rate-limitmiddleware restricts the/api/responsesendpoints to 5 submissions per minute per IP address. - Vote Deduplication:
- Authenticated Users: The backend enforces a unique constraint based on
poll_idanduser_id. - Anonymous Async Polls: The backend issues a unique
session_tokenviacrypto.randomUUID()when a user loads the poll, storing it in Postgres alongside the vote to prevent double voting from the same browser session. Client-side localStorage deduplication provides an immediate UX block.
- Authenticated Users: The backend enforces a unique constraint based on
- Authentication Security: JWTs are used for stateless auth. The short-lived Access Token (15 mins) is kept in memory/local state, while the long-lived Refresh Token (7 days) is securely stored in an
HttpOnly,Secure,SameSite=nonecookie, drastically reducing the risk of XSS attacks stealing credentials.
6. Conclusion
SwiftPolls demonstrates how modern web technologies can be orchestrated to build a highly responsive, scalable, and engaging platform. By offloading high-frequency write operations to Redis, utilizing BullMQ for asynchronous processing, and maintaining tight client-server synchronization via Socket.IO, the architecture elegantly sidesteps common bottlenecks associated with real-time applications.
The resulting product provides presenters with a powerful, zero-latency tool to engage their audiences, wrapped in a clean, user-friendly interface that requires zero friction for participants to join.