LogoNextJet
All Projects
Quiz Battle
6 min read
Visit Project

Quiz Battle

Quiz Battle

Quiz Battle

A

Renas Hassan

Sep 10th, 2025

Overview

I built Quiz Battle as a way to test out Convex and see what all the hype was about. It's a real-time multiplayer quiz game where you can create quizzes on any topic using AI and compete with your friends. The whole idea was to build something that actually needed real-time functionality, and what better way than a game where players are answering questions and seeing each other's scores update live?

The problem & motivation

I had heard about Convex online and got curious about what all the fuss was about. The idea of a database that automatically updates data in real time on the frontend sounded really cool to me. I wanted to try it out, but I needed to build something that actually required real-time functionality to really test it properly.

That's when I thought - why not build a fun game where players can challenge each other in quiz battles in real time? It seemed like the perfect use case to see how Convex handles real-time updates, user interactions, and state management. Plus, it would be something people could actually enjoy playing.

Key features & functionality

  • Create rooms/lobbies for games
  • Choose any number of topics and difficulty levels - the AI generates questions based on your selection
  • Invite friends to join your room
  • Join existing rooms with a room code
  • Play quizzes, compete with others, and see live leaderboards
  • Chat with other players in the global chat
  • Login with Google for easy access
  • AI question generation using structured output with LangChain

Tech stack

  • Next.js
  • TypeScript
  • React
  • Tailwind CSS
  • ShadCN
  • Convex

Challenges & learnings

Development Philosophy

My philosophy was to ship out an MVP fast. It's very easy to get scope creep, so rather than adding a bunch of features, a better skill is to identify which ones to strip away. Prioritize the core logic, get it working, and make sure it has a great user experience.

We don't want to get trapped in postponing the MVP release by adding more and more features to the initial release. Once you've shipped the MVP, gather feedback from early users and iterate from there. At that stage, you might notice things where you need to do complete logic rewrites or pivot, which is why it's important to get out an MVP ASAP so we wouldn't have wasted months building something only to find out later that you need to do a major rewrite.

I only realized later, after shipping and getting user feedback, that having persistent lobbies would have been nice so users don't have to create new lobbies after each game. That's when I also realized the limitations of the tech I had chosen.

Plan Before You Code

One key learning is that you need to plan out the core well before you even write a single line of code, and then choose a tech stack that supports building that.

The whole purpose of this project was to try out Convex for fun, and in doing so I learned the small quirks and limitations of the tech. Since we're building a game, the user experience needs to be very smooth and responsive. In online games, interactivity is crucial, as well as being able to interact with other players in real time.

Convex Limitations for Real-time Games

The biggest challenge was working around Convex's limitations for lobby management. Since you can't track connected clients on the backend, lobby management isn't optimal. WebSockets would have been better for lobbies and room management because you can actually see who connects and who disconnects.

It was hard to implement and sync/track correctly with Convex from the frontend for connected clients and those who leave. Challenging to track disconnections - you can track whether a user goes to another tab, but disconnections are hard to detect.

The Heartbeat Workaround

Since we can't get access to real-time WebSocket connections using Convex, I had to come up with a workaround: periodic heartbeats (every 10s). Users appear online based on recent heartbeats, not instant connection status. Therefore, there's inherent latency in detecting when someone actually leaves.

Each user periodically sends a heartbeat every 10s to the database, updating the presence record with the current timestamp. If it's been more than 10s since the last update, it means the user has left; otherwise, they're still online and present. We don't have instant disconnect detection with this approach.

One big limitation is that if we tracked who is currently in a room (i.e., a player joins a room, the game starts, and then the player leaves), we would have needed to sync the presence state with the room membership, which increases complexity. WebSockets handle these scenarios much easier and better.

Socket.IO even has built-in room management with instant notifications. We also get the added benefit of automatic detection of network issues and reconnection - we don't have to build our own complex implementation of this.

For this type of game, a 10s presence delay is acceptable for lobby management. However, for the real-time functionality like answering a question, it's instant since we send a mutation and then the data instantly syncs with the latest.

Quiz Logic Architecture

The backend is responsible for handling all the quiz logic to make sure no one can cheat and progress the state by scheduling functions. The frontend merely displays the current state and handles no quiz logic.

There are many edge cases to consider when creating the logic for lobby management: joining, leaving, disconnections, currently online users, what happens if a user creates another room while being in one? What happens if the host leaves? etc.

If a room is still in the lobby stage and all players leave, then it should be removed.

I had to rewrite the core logic of the quiz lifecycle logic three times during MVP development because of constantly changing requirements. In the end, I came up with a nice solution.

The Four-Phase State Machine

The quiz system consists of a carefully orchestrated state machine with four distinct phases, each serving a specific purpose. Each phase schedules the next phase to run after a fixed amount of time.

1. Starting Phase

The game begins when the host clicks "Start Quiz." This phase is the shortest but most critical:

  • Creates a new gameState record with phase: "starting"
  • Initializes score tracking for all players
  • Updates room status from "lobby" to "ongoing"
  • Triggers AI question generation in the background

2. Question Phase

This is where players see the question and prepare to answer:

  • Displays the question and answer options
  • Players can see the question but cannot submit answers yet
  • A timer automatically transitions to the answering phase
  • This gives players time to read and think before the pressure begins

3. Answering Phase

The high-pressure phase where players submit their answers:

  • Players can submit answers by clicking on options
  • Each submission is validated and stored immediately
  • A timeout is scheduled to automatically end this phase
  • If all players answer early, the phase ends immediately

The system checks if all players have answered and can skip the timeout, making the game flow faster when everyone responds quickly. This is one of the user experience optimizations I implemented.

4. Score Phase

Results are calculated and displayed:

  • Calculates scores for all players
  • Shows correct answers and individual results
  • Displays leaderboard updates
  • Schedules the next question or game completion

When to Use Convex vs WebSockets

Convex is great, but for the future if I'm building out a game that requires lobby management and tracking online users, I would have opted for WebSockets/Socket.IO instead. With WebSockets, we get access to the real-time connection status of the clients, for example, the connect and disconnect events.

For other use cases, like building out a chat or just a regular SaaS that merely relies on regular HTTP and APIs, Convex is a great choice both for the end-to-end type safety with remote procedure calls (it kind of feels like using tRPC), as well as for the instant data sync/updates without having to invalidate queries.

You can ship very fast with Convex, great for MVPs and very early stage startups/SaaS where speed of development is important.

I managed to ship out a working game with a really nice user experience in just a few days. I released it and sent it out to friends for testing - they loved playing it and came back with great feedback that I can iterate on in the future. It was satisfying to see people actually enjoy something I built, especially since the whole point was just to test out Convex.

Share this article with your friends!
share on twitter
share on facebook
share on linkedin
share on reddit
A

Last updated on: Sep 10th, 2025