On the surface, Happening looks simple. Friends tap the dates they can make, the app surfaces the best date, the host picks. But the moment you start writing the algorithm down, you realise it isn't simple at all. There are at least three different definitions of "best date", and they conflict with each other in interesting ways.
This post walks through how we got from a naive first attempt to the scoring system Happening uses today. If you have ever wondered why your event sometimes shows a date with five votes ranked above a date with six votes, this is the answer.
The naive version
When I first sat down to write the scheduling logic, my instinct was the same as everyone's: count votes. The date with the most yeses wins. Done.
This works for the trivial case. Six friends, the 14th gets all six, the 14th wins. But it falls over the moment you have a tie. And it falls over even harder when you think about what "I haven't responded yet" actually means.
Imagine an event with six invitees. Three have responded. One date has all three of them as available. A second date has two responders available. Naive vote counting tells you the first date is twice as good as the second. But what if the three people who haven't responded are systematically more likely to be free on the second date? You don't know. You're picking based on incomplete information and pretending it's complete.
Dealing with non-responders
The first real decision was: how do we treat the gap between "responded available" and "responded not available" and "hasn't responded at all"?
We considered three options:
- Treat non-responders as available. Optimistic. Inflates the score of every date equally, so it doesn't actually change the ranking, but it makes the absolute counts look better than they are. Misleading.
- Treat non-responders as unavailable. Pessimistic. Penalises dates that haven't yet been confirmed by everyone. This is what we tried first. It produces results that feel weirdly punitive. A date that nobody has marked as unavailable looks worse than a date with explicit confirmations.
- Treat non-responders as unknown. This is the only honest answer, and it's what we ended up with. A date with five confirmed yeses and one unknown is treated differently from a date with five confirmed yeses and one confirmed no.
The scoring function
The version that ships in v1 looks something like this. For each date, we compute three numbers:
- Confirmed available count. How many invitees have explicitly marked this date as one they can do.
- Confirmed unavailable count. How many invitees have explicitly marked this date as one they can't do.
- Pending count. Total invitees minus the two above.
The headline score for ranking is the confirmed available count. That's the primary sort key. Two dates with five yeses each are tied at the top. Among ties, we use the confirmed unavailable count as a tiebreaker, so fewer hard nos wins. If you're still tied after that, the earlier date wins.
The host also sees the pending count next to each date, so they can make an informed call. A date with five yeses and one pending is meaningfully different from a date with five yeses and zero pending, because in the first case, the maximum possible score is six.
What we deliberately didn't do
There were a few sophisticated approaches we considered and then deliberately rejected.
Weighting by historical reliability. The idea: people who are more likely to actually show up should have their votes count for more. This is technically possible, since we have the data, but we decided it was creepy. People should not be penalised in a friend group's scheduling because they once had to cancel a dinner.
Predictive modelling for non-responders. The idea: if Alice usually says yes to weekends and no to weekdays, we could fill in her missing votes with our best guess. Same conclusion. Too creepy, and the failure mode (telling the host "Alice is probably free on Saturday" and then having Alice show up to find herself committed to a thing) is much worse than just showing the gap honestly.
Optimising for "who has been left out the most". The idea: if Bob has missed the last three friend dinners, weight dates that work for Bob more heavily. This one I actually still think about. It rewards inclusivity and helps groups that have a member with a difficult schedule. But it adds a layer of opacity to the ranking. The host would see a date win that wasn't the date with the most votes, and they would have to trust our explanation. We decided clarity was worth more than cleverness, at least for v1.
The view from the host's seat
What you actually see when you open an event in Happening is this: a list of dates, sorted by how many people can make each one, with the headline number front and centre and the breakdown (confirmed yes, confirmed no, still waiting) one tap away. The top entry is the right answer in 95% of cases. The other 5% are situations where you, the host, know something the algorithm doesn't. Maybe the top date conflicts with a thing only you know about, or maybe the slightly-worse date is more convenient for the friend whose birthday you're celebrating.
That's the whole point. The algorithm does the boring part, which is counting and sorting, so you can do the human part. We are not trying to take the decision off the host's plate. We are trying to give them a decision that takes ten seconds instead of ten minutes.
What's next
The scoring system in v1 is deliberately the simplest thing that could possibly work. It will probably evolve. The most likely first change is exposing a "preferred days of the week" preference to weight scores by. Many groups have an implicit "we always do Saturdays" rule that the algorithm could learn explicitly. But every change has to clear the same bar: does it make the answer more useful, or does it just make the algorithm more impressive?
The goal has always been the same. Help people get a dinner booked.
