# courtScore

Case study 02 | Own product | Live

It started as a fix for one pickleball group's unfair rotations. Two years and six versions later, it's a tournament platform for pickleball and badminton that organizers use to run real events, with live scores on everyone's phone.

- Role: Product, design, build and ongoing maintenance. Solo.
- Timeline: October 2024 to now
- Stack: Python, Flask, PostgreSQL, Mailgun, Web Push, Railway
- Live site: [courtscore.ca ↗](https://courtscore.ca)

198 games scored live

63 players across 30+ tournaments

900+ automated tests

600+ commits since October 2025

The problem

## Somebody always sat out twice. Somebody always got the same partner.

Running a social round robin meant one person juggling names on a whiteboard, guessing who should play next and tallying scores by hand. Pairings drifted, sit-outs weren't fair, and **nobody trusted the standings **at the end of the night.

Before

- × Rotations planned on a whiteboard
- × Same partners, same opponents
- × Scores tallied by hand
- × Results lost when the session ended

After

- → Fair rotations generated in a tap
- → New partners before any repeats
- → Scores entered from any phone
- → Results emailed and saved to profiles

How it got here

## Six versions in two years

Every version was used at real sessions, and each one fixed what players complained about in the last. The algorithm survived every rewrite. Almost nothing else did.

- Oct 2024

iOS · Swift Pickleball Scheduler First attempt: an iPhone app to plan one group's rotation.

- Oct 2024

iOS · SwiftUI Two SwiftUI rewrites Rebuilt twice the same month to get the pairing logic right.

- Dec 2024

HTML · JavaScript Web version Moved to the browser so anyone in the group could open it.

- 2025

Python · Flask PB Scheduler The first server version, run at sessions.

- Oct 2025

Flask · PostgreSQL ShuttleScheduler Opened up to badminton, with saved players and finals.

- Oct 2025 to now

Flask · PostgreSQL · Railway courtScore Rebuilt and rebranded: accounts, guests, three modes, live scores.

What I built

## What it does

Three tournament modes exist because three different organizers asked for three different things, not because a roadmap said so.

### Run the event

- → Rotating, fixed-team and manual modes
- → Groups, playoff brackets and best-of formats
- → Co-admins and an organizer-only score lock
- → No account needed to start as a guest

### Play the event

- → Scores entered from any phone
- → Live standings and finals
- → Share link for spectators
- → Installable app with push notifications

### After the event

- → Results emailed to every player
- → Global leaderboard and player profiles
- → Tournament history and PDF export
- → Super-admin portal for oversight

The hard part

## Fair rotations for any number of players.

The scheduler is the piece that started it all. For every game it decides who plays, who partners whom and who they face, so that over a session** everyone gets a fair share** of games and new partners before anyone repeats.

Rotating partners Fixed teams Manual Groups Playoff brackets

1

Sit-outs first

Players who sat out the last game get priority for the next one.

2

New partners

A pair can only team up again once both have partnered everyone else.

3

Fresh opponents

Teams that already met are avoided while new match-ups exist.

4

Live standings

Every score updates standings on every phone in the room.

Odd numbers: sit-outs rotate so the extra player changes every game.

Guardrail: generation is capped, so even a 100-player event can never spin forever.

How it's built

## Engineering decisions

### Pure algorithm

The scheduler is plain functions with no database calls, so it's tested on its own.

### Guests first

Anyone can run a full event without signing up, then claim it later.

### Locked results

Organizers can lock scores so a finished game can't be quietly edited.

### Self-cleaning

Unclaimed guest events expire on a schedule, handled by a cron job.

### Email that lands

Results go out through Mailgun with a fallback provider.

### Feels native

A service worker and push notifications let it install and behave like an app.

Screens

![courtScore on three phone screens](https://abbasvirjee.com/static/img/courtscore-wide.webp)

Fixtures, rankings and a personal record, made for a phone at the court.

![The courtScore leaderboard on a phone](https://abbasvirjee.com/static/img/courtscore-a.webp)

Rankings across every tournament, by titles or by win rate.

![A personal record in courtScore on a phone](https://abbasvirjee.com/static/img/courtscore-b.webp)

A personal record told in plain sentences, not a table of numbers.

It started with one group's sessions and now runs full tournaments.

courtScore has scored 198 games for 63 players across more than 30 tournaments. I still run it, and I keep shipping what players and organizers ask for.
