DEV Community

Kim
Kim

Posted on

How I Built a Personal Trainer Matching Algorithm (That Actually Works)

Most people find their personal trainer the same way they find a plumber: Google Maps, check a few reviews, and hope for the best.

I thought we could do better.

The Problem Nobody Talks About

Here's what happens when you search "personal trainer Toronto":

  1. Google shows you whoever paid for ads
  2. Yelp shows you whoever has the most reviews (correlation with quality: questionable)
  3. Instagram shows you whoever posts the most shirtless photos
  4. Your friend recommends whoever they like — which might be a terrible fit for you

None of these answer the actual question: will this trainer's coaching style work for me?

A powerlifter who wants someone screaming at them during sets needs a very different trainer than someone recovering from an injury who wants gentle guidance. Both are valid. Neither will be happy with the other's trainer.

The Hypothesis

What if we could match people to trainers the same way dating apps match people — not on location or price, but on compatibility?

I built FitMatch to test this. It's a 60-second quiz that scores you against 30 real Toronto personal trainers across 5 dimensions.

The Five Dimensions

After researching what actually matters in a trainer-client relationship, I landed on five scoring dimensions:

Dimension Weight Why It Matters
Training Goal 30% A bodybuilding coach and a rehab specialist are different species
Coaching Style 20% Drill sergeant vs. supportive guide — this is the #1 mismatch
Feedback Style 20% Some people want data and metrics. Others want 'great job, keep going'
Programming Structure 20% Rigid periodized plans vs. flexible 'let's see how you feel today'
Check-in Frequency 10% Daily accountability vs. weekly touchpoints

The weights aren't arbitrary. Training goal gets 30% because if a trainer specializes in marathon prep and you want to powerlift, nothing else matters. Check-ins get 10% because it's the easiest dimension to adjust.

The Scoring Math

Each dimension produces a score from 0-100 based on how closely the user's answer matches the trainer's profile.

fitScore = (goalMatch × 0.30) +
           (styleMatch × 0.20) +
           (feedbackMatch × 0.20) +
           (structureMatch × 0.20) +
           (checkinMatch × 0.10)
Enter fullscreen mode Exit fullscreen mode

For each dimension, matching works like this:

  • Exact match: 100 points
  • Adjacent preference: 50-75 points (e.g., 'structured' trainer with a 'somewhat flexible' user)
  • Opposite ends: 0-25 points

The result is a score from 0-100 for every trainer, which maps to labels:

  • 🟢 75-100: 'Strong fit' — your styles align
  • 🟡 50-74: 'Decent fit' — could work with some adjustment
  • 🔴 Below 50: 'Different styles' — probably not your person

Why Not Use AI/ML?

I hear you. 'Why not throw embeddings at this?'

Because the dataset is 30 trainers, not 30,000. Machine learning on 30 data points is just overfitting with extra steps. The weighted scoring approach is:

  • Transparent: Users can understand why they matched
  • Debuggable: When a match feels wrong, I can trace exactly which dimension caused it
  • Tunable: Adjusting weights based on user feedback is trivial
  • Fast: No model inference, no API calls — runs entirely client-side

Sometimes the boring solution is the right solution.

The Cold Start Problem

The tricky part wasn't the algorithm — it was getting trainer data. Trainers didn't sign up for this. I had to:

  1. Research 30 real Toronto trainers manually
  2. Infer their quiz answers based on their websites, social media, and client testimonials
  3. Build profiles that honestly represent their coaching style

This is the uncomfortable truth about two-sided marketplaces: someone has to go first. My bet is that trainers will opt in after they see 'someone took a quiz and matched with you as a Strong Fit — want their contact info?'

That email pipeline isn't wired yet. Right now, FitMatch is a discovery tool: take the quiz, browse your matches, visit their websites directly.

The UX Decisions

A few things I got right (I think):

One question per screen. Early versions showed all 6 questions on one page. Completion rates were terrible. One-per-screen with tap-to-advance felt more like a personality quiz and less like a form. Completion jumped immediately.

No account required. Zero friction to results. You can take the quiz in 60 seconds and see every trainer ranked. Email capture is optional ('let your top matches know you're interested').

Show all 30 trainers. My instinct was to show only the top 5. But users want to scroll. Seeing a trainer ranked #22 with a 45% fit score actually increases trust in the algorithm — 'oh, it's not just showing everyone as a perfect match.'

The Stack

Nothing fancy:

  • Next.js — pages + API routes
  • Vercel — hosting and edge functions
  • No database — trainer profiles are static JSON (for now)

Total infra cost: $0/month.

What I'd Do Differently

Weight validation. The 30/20/20/20/10 split was my best guess based on research. I should have A/B tested different weight distributions from day one.

Trainer opt-in first. Bootstrapping with inferred profiles was pragmatic but creates a trust gap. If I were starting over, I'd cold-email 10 trainers first and build profiles collaboratively.

Fewer dimensions. Five dimensions might be over-engineering it. I suspect goal + coaching style alone (two dimensions) would get you 80% of the matching accuracy. The other three add precision but also add quiz length.

Try It

If you're in Toronto and looking for a trainer (or just curious), take the quiz. It's free, takes 60 seconds, and doesn't require an account.

If you're a developer building a matching/recommendation system: weighted scoring is underrated. It's simple, interpretable, and works surprisingly well at small scale. Save the ML for when you have the data to justify it.


I'm building this as a solo side project. If you want to follow along or have feedback, drop a comment — I'm actively iterating based on what I hear.

Top comments (0)