Project
Movie Poll
Cast a vote and watch it move — this poll runs over a persistent WebSocket connection through API Gateway, Lambda, and DynamoDB. No polling, no refreshing: when a vote lands anywhere, every open tab watching this page updates on its own.
Demo
If You Were a Movie, What Movie Would You Be?
Pick the one that fits. Your vote goes out over an open WebSocket connection to a Lambda function, updates the tallies in DynamoDB, and gets broadcast back to every connected browser — including this one — in real time.
Each option pairs a movie with a one-line description of the mood or energy behind it — not a plot summary, more of a vibe check. Pick whichever one matches how you're feeling right now.
Privacy notice: your browser stores a random,
anonymous ID in localStorage — not a cookie — so the
poll remembers which option is yours. Nothing personally
identifiable is collected, and it's never sent anywhere beyond
this one poll.
Changed your mind? Tap a different option any time — your vote moves with you.
Try this: open this page in a second tab and vote there. Watch this tab's chart update on its own — that's the WebSocket push, not a page refresh.
Loading live results…
Live Activity
Architecture
How It Works
This isn't a series of one-off requests — it's a single WebSocket connection that stays open the whole time you're on this page. Step 1 happens once, when you arrive. Steps 2 through 5 repeat every single time anyone votes, without ever reconnecting. Click any numbered step to jump to it, or let it play through on its own.
-
1
Connect
Your browser opens a WebSocket connection to API Gateway when this page loads — this happens once. It stays open, ready for anything that happens next, until you leave.
-
2
Vote sent
Tapping an option sends a small message over that already-open connection to a Lambda function — no new request, no reconnecting, no page reload.
-
3
Tally updates
Lambda records your choice and updates the shared vote counts in DynamoDB — changing your vote moves the count instead of adding a duplicate.
-
4
Tallies read back
That same Lambda function reads the fresh totals for every option back out of DynamoDB, ready to send.
-
5
Broadcast
Lambda pushes the updated results back out through API Gateway to every open connection at once — including yours, without you doing anything.
Steps 2 through 5 can repeat any number of times — that's what happens every time anyone votes. The connection itself only closes when you actually leave the page, at which point it disconnects cleanly rather than staying open in the background.
Use Cases
Where This Pattern Fits
This real-time push pattern isn't specific to a movie poll — the same WebSocket API + Lambda + DynamoDB combination covers anything that needs to update instantly across multiple viewers at once.
Live audience Q&A and polling
Conference talks, webinars, or classrooms where the audience votes or asks questions and everyone sees results shift live, without refreshing anything.
Real-time dashboards
Order status boards, live metrics, or ticket queues that need to reflect changes the instant they happen instead of on a fixed refresh interval.
Collaborative & multiplayer features
Shared cursors, "who's online" presence indicators, or lightweight multiplayer state — anywhere multiple people need to see the same live picture at once.
Pricing
What This Actually Costs
Every piece of this architecture is pay-per-use, but WebSocket connections add a cost dimension a plain HTTP API doesn't have: you pay for connection time, not just requests. These are estimates based on public AWS list pricing, not a guarantee.
What drives the cost
- API Gateway (WebSocket) — billed per million messages sent, plus per million connection-minutes for every second a socket stays open, even idle.
- Lambda — three functions (connect, disconnect, vote), each billed per request and per millisecond of compute time.
- DynamoDB — on-demand pricing per read/write; the vote broadcast currently scans the full connections table on every vote, which is fine at demo scale but is the first thing that'd need to change at real scale.
- S3 + CloudFront — a few cents a month at most, same as the rest of this static site.
Design Decisions
Why I Built It This Way
A few choices here aren't the only way to build this — here's the reasoning behind them.
A WebSocket API instead of polling
Having the browser poll for updates every few seconds would have been simpler to build. I chose the WebSocket API anyway, because the payoff is worth the extra complexity: two tabs on this page update each other instantly, with no visible lag — a much stronger demonstration of real-time push architecture than a polling loop would be.
Three DynamoDB tables instead of one
Connections, votes, and tallies each answer a different question, so each gets its own table with its own natural key. DynamoDB supports "single-table design" for cases where minimizing table count matters at scale, but for a project this size, three small, clearly-named tables are easier to read and reason about than one table doing three jobs at once.
Separate Lambda functions per event
$connect, $disconnect, and
vote/sync each get their own
function instead of one large Lambda branching on event
type. Smaller functions are easier to read in isolation,
and CloudWatch logs stay naturally separated by concern
instead of interleaving unrelated events.
Sync-after-connect, not push-from-connect
Pushing initial results to a client from inside its own
$connect invocation is an unreliable pattern in
API Gateway's WebSocket API — a connection isn't always
immediately postable during its own connect event. Instead,
the client asks for results in a separate message right
after opening, which sidesteps that race entirely.
Sequential writes instead of a transaction
Recording a vote and updating both the new and old option's tallies happens as two or three separate DynamoDB calls, not one atomic transaction. A production vote system handling real stakes should use a transaction so a mid-request failure can't leave the tally out of sync. For a demo poll, the added complexity wasn't worth it — and that tradeoff is documented directly in the code, not left as a silent shortcut.
Architecture Diagram
Full Architecture Diagram
The complete AWS architecture diagram for this project, built with draw.io. Click it to expand full screen.