Product design · 2026

Stacked

Partner matching for pickleball doubles, built spec-first. A working prototype of the matching model, and a written record of every design decision behind it — including the ones I got wrong and had to revise.

Try the prototype Read the spec Tradeoff register GitHub
01

The problem

I compete in pickleball tournaments at the 5.0 level, so this is a problem I run into myself. The sport is growing fast and there is no shortage of players. What there is a shortage of is signal.

You cannot tell who is currently looking for a partner. Asking someone implies you want to replace the partner they already have, so nobody asks. And fit gets guessed from a single skill rating, which is why two players of identical level can be a terrible team. Doubles is a role sport: two bangers with nobody crashing the net lose to a drop-and-grind pair every time.

The scarce thing isn't players. It's knowing who is open, and whose game fills the gaps in yours.

02

Who it’s for

The existing way people find partners is open play — rotate through dozens of players over months and gradually work out who you click with. It works, and I didn't want to replace it. Instead I scoped the product to the two groups it fails completely.

Persona A

The travelling tournament player

Registered for an event in a city where they know nobody. High intent, hard deadline, willing to risk a stranger because the alternative is not playing.

  • Geography is inverted — their home court is irrelevant, the event location isn't
  • Schedule overlap is meaningless; they want one weekend, not a routine
  • Reputation outranks playstyle when you're committing a whole day to a stranger
Persona B

The improver without a network

Wants to get better but doesn't attend open play — intimidating, wrong schedule, or they want structured drilling rather than rotating rec games.

  • Schedule and geography dominate — if you can't meet, nothing else matters
  • Needs to play with people better than them, which the rating system quietly blocks
  • Success is a recurring drilling partner, not a single match

These two want nearly opposite things from the same ranking. That single observation forced the biggest structural decision in the product: the scoring weights are selected by intent rather than shared. Schedule overlap is the heaviest term for a recurring local partner and undefined for a one-weekend tournament team.

03

Why similarity is the wrong model

The obvious approach is to score people by how alike they are. That is wrong here, and wrong in a way that produces confidently bad matches. Fields fall into three categories, and each needs different maths.

Align
Both should be similar. Skill rating, competitive intensity, schedule, geography, commitment level.
Complement
Difference is the advantage. Court side, handedness, net role, poaching appetite. A right-side player and a left-side player is a team; two left-side players is not.
Demand / supply
My need must be met by your trait — asymmetric, and checked in both directions. What you want from a partner is a different question from what you bring, so temperament is two fields, not one.

A similarity score gets the first row right, the second exactly backwards, and cannot express the third at all.

I also chose named archetypes over a black-box distance function — Bang & Crash, Two Grinders, and anti-patterns like Collision Course for two players who both poach. It is slightly less precise, and it means every match explains itself in one line. An explainable 80 beats an inscrutable 95: people trust a score they can argue with, and the explanation teaches them the sport, which is its own reason to come back.

04

The constraint I got backwards

My first draft assumed something that sounds obvious: everyone wants a partner rated above them, and nobody wants one rated below. I built the whole acceptance model on that asymmetry. It's wrong.

DUPR — the sport's rating system — sets an expected score from the average of the two partners' ratings. Your rating then moves on the gap between the actual result and that expectation, which means you can win a match and lose rating.

A much higher-rated partner raises the bar you have to clear. Meeting expectation earns you almost nothing; falling short costs you.

So a player optimising to climb should decline a partner far above them. The rating-optimal partner isn't the strongest available — it's an underrated one, whose rating sits at or slightly below yours but whose actual play exceeds it.

The fix was to stop treating preference direction as universal and derive the acceptance band from what each player is actually optimising for — winning the event, climbing, improving, or just having a good time. Two players optimising differently can now match with each other, which the original model would have filtered apart.

The second half of the fix was rating stakes. The entire problem only exists because results get uploaded. Letting a session be declared unrated costs the stronger player nothing and unlocks the cross-level play that Persona B needs and the rated market structurally suppresses. In the prototype, toggling it takes the candidate pool from 11 players to 18.

05

Decisions, and what they cost

Every non-obvious decision goes in a register with its rejected alternative, its reasoning, its cost, and the condition that should make me reopen it. Twenty-six entries so far. A decision with no downside listed usually means I hadn't actually examined it.

T-06

Profile hiding, deferredrevised

Chose
Ship the visibility controls, but leave them switched off until a city reaches critical mass.
Why
Hiding people only helps once there are more candidates than anyone can review. Below that, the constraint is supply — and a filtered board looks abandoned. The feature that protects strong players at scale is the one that starves the product at launch.
Cost
Early strong players get more unwanted invites — exactly the group most likely to leave. Handled with per-sender rate limits instead.
T-03

Availability has to expiresettled

Chose
Every "open to partners" signal carries a required expiry, capped at 60 days, and reverts silently.
Why
Every partner directory dies the same death: someone marks themselves available, finds a partner offline, never updates. New users message five ghosts, hear nothing, and leave.
Cost
Real friction, and the board looks emptier than the true number of interested players. Worth it — a short board where everyone replies beats a long board of ghosts.
T-19

Deliberately showing worse matchessettled

Chose
Around one in eight surfaced matches is deliberately off-model.
Why
You only ever see how pairs worked out for pairs that actually formed — and the algorithm chose those. Without deliberate exploration the model just confirms its own assumptions forever.
Cost
Some users see a worse match than I could have shown them. It's a real price, paid on purpose, and the kind that quietly gets cut the first time engagement dips.
T-24

Underrated players vs. sandbaggingopen

Tension
Players who beat their rating are measurably valuable partners. But publishing "this person is underrated" is one step from teaching people to manipulate their rating.
Leaning
Surface it as recent form, never as a ranking key, never as an arbitrage opportunity.
Cost
Getting this wrong damages the rating ecosystem the whole product depends on. Still unresolved, and flagged as such.
06

What building it taught me

The prototype runs the real matching model against sixty seeded players. It exists to answer one question — do the matches feel right? — and it surfaced two things the written spec could not.

A weight can be live in the config and dead in practice. Schedule overlap was the heaviest factor for local matching, defined as needing four shared hours a week. But players only list four to six hours of availability in total, so demanding four shared hours demanded near-total alignment. The factor collapsed to roughly zero for almost every pair, quietly nullifying the most important term — while the overall score still looked completely plausible. Now I check the distribution of each component, not just the final number.

Filters compound much harder than any one of them looks. Of sixty players, only nineteen survived the default view. Each filter was individually defensible; stacked together they removed two-thirds of an already-small pool. That became its own open entry in the register — intent is probably better as a ranking penalty than an elimination, because someone who lists "drilling" may well say yes to a casual game.

See it working

Sixty seeded players, the full matching model, and an explanation attached to every score. Set your objective to “climb” and watch the strong partners disappear.