Building for People Who Had Already Been Abandoned Once

Most product briefs start with a market. This one started with a thread on Reddit.
QuizUp shut down in 2019. Before that it had been genuinely enormous: built in Reykjavik, launched on iOS in 2013, more than a hundred million users, twelve hundred topics. You could be level 83 in Friends and a complete beginner at geography, and that combination was somehow an identity. Then it was acquired, then the ads and the energy bars arrived, then it was switched off.
Years later, r/QuizUp was still active. People were still posting in the subreddit of an app that no longer existed. One of the comments that ended up shaping the product read, roughly: I just want somewhere to compete in real time on topics I care about, without ads, without energy bars, without being sold gems. Underneath it, someone had written that they had played 43,000 games and still missed it every day.
That is an unusual thing to find. Not a market gap, exactly. More like a room where people were still turning up years after the lights went out.
Quizzy was built for those people. I have written up the case study with the metrics and the stack; this is the longer version, about what that requirement actually did to the engineering.
The requirement nobody writes down
Here is the thing about building for a community that has already lost something. They are not evaluating your product on its merits. They are watching for the moment it lets them down, because they have been here before and they know how it ends.
Which turns an ordinary technical problem into the central one.
A head-to-head trivia game is two people answering the same questions at the same time. That is the entire appeal, and it depends completely on there being a second person. Before launch, there is no second person. So the first user opens the app, taps play, watches a spinner, and matches with nothing.
For a normal product that is a cold start problem and you solve it with marketing. For this audience it is something worse. It is confirmation. They came back to a thing that promised to be what they lost, and found an empty room, which is exactly what they were braced for. You do not get a second attempt at that.
So the requirement, unwritten anywhere in the brief, was: nobody ever taps play and gets nothing. Not on day one, not at 3am, not in a timezone where the other twelve users are asleep.
Building an opponent

The obvious answer is a single player mode. We did not want one, because a single player mode is an admission. It says: there is nobody here, so here is something else instead. Every time a user picks it they are being reminded of the thing that is missing.
The answer we went with was an opponent that is always available.

The important detail is not that there is a bot. Plenty of games have bots. It is where the bot lives.
It joins the same Firestore game room a human opponent would. It writes its answers into the same fields, on the same document, that a human client writes to. It marks itself ready the same way. From the perspective of every other system in the game, matchmaking, scoring, results, rematch, statistics, a bot match and a human match are indistinguishable, because structurally they are the same match.
That was a deliberate decision and it cost more than faking it in the client would have. What it bought is that there is exactly one implementation of a game. There is no second code path that only runs when the lobby is empty, quietly rotting because nobody thinks to test it. When we changed how scoring worked, it changed for both. When we fixed a bug in the results screen, it was fixed for both.
An opponent that can lose, and admits what it is
Two things make the bot work, and both of them are about respect rather than cleverness.
The first is that it is beatable. Accuracy is a parameter rather than a behaviour, defaulted to answering correctly about half the time, and it waits a human amount of time before each answer instead of responding instantly. An opponent that never misses is miserable. One that always misses is insulting, and obvious within two rounds. The interesting space is in between, and having it as a number meant it could be tuned rather than rewritten.
The second matters more. The bot never pretends to be a person.

It is called Bot. On the scoreboard during the match, and on the results screen afterwards, with its own rank next to the name. Seven rounds, points per round, the same breakdown you get against a human.
There was a version of this where the bot gets a plausible username and a generated avatar and the user never knows. It would have been easy and it would have been the wrong call for this specific audience. These are people who left an app because it started treating them as a monetisation surface. Opening with a small deception, on the thing they came back for, would have been a strange way to begin a relationship built on the promise of not doing that.
So the product’s position was: we cannot promise there is always somebody online, but we can promise you will always get a game, and we will tell you what you are playing.

The part where this bites you
Now the honest cost, because I have described a pattern and somebody is going to copy it.
Bot matches are real matches. That is the whole point. But it means bot users accumulate real records: games played, wins, losses, statistics, entries in the same collections everything else reads from. Which means they turn up in leaderboards, in win rates, and in the aggregate numbers the app shows people about themselves.

We ended up writing tooling to strip bot records out of the statistics collections, which is exactly the kind of maintenance job that exists only because of a design decision made months earlier, and which never appears in anybody’s estimate.
If you build it this way, plan for that on day one. Decide up front whether a bot game counts toward a player’s record, and enforce it at write time rather than cleaning up afterwards. We learned it in the other order.
Ten thousand questions and the repeat problem
The second thing this audience would not forgive is repetition.
QuizUp had twelve hundred topics, many of them user-created, and depth was the reason people stayed. Someone with 43,000 games behind them is going to notice a recycled question inside their first week, and a quiz app that repeats itself is a quiz app you have finished.
The target was 10,000 questions across 100+ topics at launch, which is a volume problem no small team writes by hand. So questions were generated in batches with Gemini, written out to JSON with metadata for topic, subtopic and difficulty, and appended incrementally so a batch could fail without losing the run.
Generation solves volume and immediately creates an accuracy problem, because a confidently wrong trivia question is worse than no question at all. In a trivia game, being wrong is the one unforgivable bug: the entire product is a claim to know things. So the pipeline deduplicates, and then everything waits in the CMS for a human to review before it can reach a player. The moderation tooling exists precisely because the generation is automated.
That combination, machine volume with a human gate, is the only way I know to get a five figure question bank out of a team this size without eventually publishing something embarrassing.
Three mobile stacks before committing
One more decision worth reporting honestly, because the repository still contains the evidence.
There are four mobile directories in that codebase. A React Native build on Expo, an Ionic build, and the SvelteKit and Capacitor build that shipped. Two of them were taken far enough to judge properly and then abandoned.
That looks like indecision and I would defend it as the opposite. The deciding factor in the end was not framework quality, it was that the app, the moderation CMS and the marketing site were all going to be maintained by the same small team. Choosing SvelteKit meant one language and one component model across all three, so a person moving between them was not also switching mental models. For a large team with separate frontend groups that argument is worth very little. For this team it was worth more than any individual framework feature.
Better to spend two weeks finding that out than eighteen months living with the wrong answer.
What happened
Quizzy was built over about ten months, from March 2025 to January 2026. Real-time multiplayer across iOS, Android and web from one codebase. A moderation CMS. A generation pipeline. Topic levels, followers, chat, rematches, per topic statistics. A waitlist that was filling up toward a launch threshold of a thousand founding members.
The technology and the question database were sold to a company we are not able to name yet.
I want to be careful about how I say the next part, because it is not a criticism of anybody, and because I do not want to oversell something I cannot yet point to. But I will say this much: whoever ended up with it understood immediately what the platform actually was and who it was built for, which is not something every acquisition gets right. We are not worried about where it landed. I think the trivia space is going to hear about this again, and I do not think it will take long.
The founding blog post promised the community that this one would not disappear. Almost every product makes some version of that promise and very few get to keep it, and the reason is usually structural rather than a failure of intent. Companies get sold. That is a normal outcome, often a good one for the people who spent years on it, and it is not the same thing as the cynicism that killed the original.
The subreddit is still there. The need that produced that Reddit comment did not go away when QuizUp shut down, and it did not go away when Quizzy changed hands. Somewhere there is still a person who played 43,000 games of something and has not found the replacement.
We got ten months of trying to be that replacement. We are glad the people who have it next seem to understand the assignment, and until we can say more, we will be watching for the news same as everyone still posting in that subreddit.
What I would take from this
The emotional requirement is usually the technical one. The most human fact about this project, that its users had been let down before, produced its hardest engineering decision. If you can find the sentence that explains why people will leave, you have usually found your architecture.
Route the fallback through the real path. The bot is not special-cased anywhere. One implementation of a game, used by everyone, is worth the extra work at the start and it compounds every time you change something.
Decide what counts before it counts. Bot matches polluting real statistics was entirely predictable in hindsight. Whether synthetic activity is included in user-facing numbers is a question to answer at write time, not with a cleanup script later.
Automate the volume, gate the truth. Generation solved a problem no small team could brute force. The review step is what made the output safe to publish, and skipping it would have undermined the exact quality the product was selling.
Being honest with users is a feature, not a cost. Labelling the bot as a bot was the cheapest possible way to signal what kind of product this intended to be, to an audience specifically watching for signs of the opposite.
If you are building something where the users have already been burned once, we should talk. That constraint changes more of the architecture than most people expect.
Want to discuss architecture?
We help funded startups make the right technical decisions from day one.