Mentorship Platform HLD: Matching Mentees to the Right Mentor
How a mentorship platform like Preplaced matches people: hard filters that remove the unqualified, a weighted skill-overlap score that ranks the rest, and the search-match-book architecture.
A mentee lands on Preplaced (or ADPList, MentorCruise) and types what they want: "I'm targeting backend system-design interviews, my budget is ₹2000 a session, and I want someone well-rated." Out of thousands of mentors, the platform has to surface the handful worth their time, best first. That's a matching-and-ranking problem, and it splits cleanly into two stages every recommendation system shares: hard filters that throw out anyone who can't qualify at all, then a soft score that orders whoever's left. Get that two-stage shape right and "find me a mentor" becomes a few lines of code.
This is the inside of a mentorship marketplace. The signature problem is constrained ranking: filter on the non-negotiables, then rank on fit. (The booking half — slots and payment — is the Topmate write-up; this is the discovery half.)
Let's start nowhere near a computer
Imagine hiring a tutor through an agency. You hand the clerk a card: the subjects you need, the most you'll pay, and "no one below four stars." The clerk does two passes. First they remove the impossible — too expensive, too poorly rated, teaches the wrong subject — sweeping those cards off the table entirely. Then, among the cards that survive, they sort: the tutor who covers the most of your subjects goes on top, and ties are settled by who's better rated.
That first sweep is hard filtering (a card either qualifies or it doesn't), and the second is soft scoring (a graded ranking among those that do). Mixing them up — ranking everyone, then hoping the bad ones sink — is the classic mistake; a too-expensive mentor should never appear at all, no matter how brilliant.
Where this exact shape shows up
- Preplaced, ADPList, MentorCruise, Topmate discovery — all match mentees to mentors this way.
- It's the universal filter-then-rank of search and recommendations: job matching, dating, product search.
- The booking that follows a match is the Topmate slot/payment flow.
Step 1 — Functional requirements (sentences first)
- A mentee searches with their goals: desired skills, budget, minimum rating.
- The platform filters out mentors who don't qualify (price, rating, no relevant skill).
- It ranks the qualifying mentors by fit, best first.
- A mentee picks one and books a session.
- Results are deterministic — the same query gives the same order.
The load-bearing requirement is "filter, then rank by fit." It's what makes this a two-stage matcher rather than a flat list.
Step 2 — Non-functional requirements
- Relevance. The top results genuinely fit the mentee's goals — overlap matters more than vanity metrics.
- Determinism. Identical queries return identical ordering; no random shuffling of equals.
- Low latency. Matching runs in milliseconds even over a large mentor pool.
- Fairness / explainability. A mentor's rank should be explainable from the score, not a black box.
- Scale. Many mentees querying a growing mentor catalog.
Listing them is the easy half; the design only earns them if it fulfills them:
| Requirement | How this design fulfills it |
|---|---|
| Relevance | skill overlap dominates the score (×100); a mentor with no overlap is filtered out — Steps 3, 4 |
| Determinism | a fixed score formula plus an id tie-break — equal scores order identically — Step 4 |
| Low latency | filter + score is a single linear pass over the pool — Steps 4, 7 |
| Explainability | the score is a transparent overlap×100 + rating — Step 4 |
| Scale | pre-filter with an index, score only the candidates; shard by skill — Step 7 |
Every trade-off below is chosen to keep one of these.
Step 3 — Two stages: hard filters, then soft score
A hard filter is a yes/no gate on a non-negotiable: over budget, below the rating bar, or zero overlapping skills → removed entirely. A soft score is a graded number that ranks whoever passes. Keeping them separate is the whole design: filters guarantee every result is acceptable, the score makes the order good. The score weights skill overlap far above rating, so a mentor who covers more of your goals always outranks a higher-rated mentor who covers fewer — fit beats vanity.
Step 4 — The matcher
Here's the engine. Filter on the three hard constraints, then sort survivors by overlap×100 + rating, with mentor id as the final tie-break:
package dev.fiveyear.mentorship;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* The matching core behind a mentorship platform like Preplaced: given a mentee's goals
* (the skills they want, their budget, a minimum rating bar) and a pool of mentors, return
* the mentors worth showing, best first. It works in two stages — HARD FILTERS remove
* anyone who can't qualify at all (over budget, under the rating bar, no overlapping
* skill), then a SOFT SCORE ranks the survivors. The score weights skill overlap far above
* rating so domain fit dominates, with rating breaking ties and mentor id as a final, fully
* deterministic tie-break. Ratings are stored as tenths (47 == 4.7 stars) to keep the
* arithmetic exact and the ranking reproducible.
*/
public class MentorMatcher {
public static final class Mentor {
final String id;
final Set<String> skills;
final int ratingTenths; // 0..50, e.g. 47 == 4.7 stars
final int pricePerSession;
public Mentor(String id, Set<String> skills, int ratingTenths, int pricePerSession) {
this.id = id; this.skills = skills; this.ratingTenths = ratingTenths; this.pricePerSession = pricePerSession;
}
}
public static final class Query {
final Set<String> desiredSkills;
final int maxBudget;
final int minRatingTenths;
public Query(Set<String> desiredSkills, int maxBudget, int minRatingTenths) {
this.desiredSkills = desiredSkills; this.maxBudget = maxBudget; this.minRatingTenths = minRatingTenths;
}
}
/** Mentors that pass the hard filters, ranked best-first by the soft score. */
public List<String> match(List<Mentor> mentors, Query q) {
List<Mentor> eligible = new ArrayList<>();
for (Mentor m : mentors) {
if (m.pricePerSession > q.maxBudget) continue; // hard: affordable
if (m.ratingTenths < q.minRatingTenths) continue; // hard: meets the bar
if (overlap(m, q) == 0) continue; // hard: at least one shared skill
eligible.add(m);
}
eligible.sort((a, b) -> {
int byScore = Integer.compare(score(b, q), score(a, q)); // higher score first
if (byScore != 0) return byScore;
return a.id.compareTo(b.id); // deterministic tie-break
});
List<String> out = new ArrayList<>();
for (Mentor m : eligible) out.add(m.id);
return out;
}
/** Soft score: skill overlap dominates (×100), rating breaks ties. */
public int score(Mentor m, Query q) { return overlap(m, q) * 100 + m.ratingTenths; }
int overlap(Mentor m, Query q) {
int n = 0;
for (String s : q.desiredSkills) if (m.skills.contains(s)) n++;
return n;
}
}The ×100 weight is the design decision in one number: two skills (200) always beats one skill (100) no matter the rating, while rating (0–50) only ever decides within the same overlap tier. The id tie-break is what makes the ranking deterministic — two equally-scored mentors never swap places between requests.
Step 5 — The architecture
A mentee's query hits the match service, which reads the mentor profiles (skills, rating, price), runs filter-then-rank, and returns an ordered list. The mentee picks one, and the flow hands off to booking — the slot-lock-pay-confirm engine from the Topmate write-up — and finally the live session (video + shared notes). The match service is read-only over profiles; it never mutates state, which makes it trivial to cache and scale.
Step 6 — Trade-offs (each one keeping an NFR)
| Decision | The tempting alternative | Why ours wins | Keeps |
|---|---|---|---|
| filter then rank (two stages) | one combined score with penalties | a disqualifier (over budget) can never leak into results | relevance |
| overlap weighted ×100 over rating | weight rating equally | domain fit dominates; a great mentor for the wrong topic ranks low | relevance |
| deterministic id tie-break | leave equal scores unordered | identical queries return identical order; testable, stable | determinism |
| transparent additive score | an opaque ML black box | every rank is explainable from the formula | explainability |
| stateless read-only match service | match coupled to booking | cache and scale matching independently of writes | scale, latency |
The complete implementation
The matcher is the engine. Here's the driver that proves it — each hard filter, the overlap-beats-rating ranking, budget tightening, an impossible bar, and the deterministic tie-break:
package dev.fiveyear.mentorship;
import java.util.List;
import java.util.Set;
import dev.fiveyear.mentorship.MentorMatcher.Mentor;
import dev.fiveyear.mentorship.MentorMatcher.Query;
public class Main {
public static void main(String[] args) {
MentorMatcher matcher = new MentorMatcher();
Mentor amir = new Mentor("amir", Set.of("java", "system-design", "kafka"), 48, 1500); // 2 overlaps
Mentor bina = new Mentor("bina", Set.of("java", "system-design"), 45, 1200); // 2 overlaps, lower rating
Mentor cory = new Mentor("cory", Set.of("java"), 49, 1000); // 1 overlap, top rating
Mentor dina = new Mentor("dina", Set.of("python", "ml"), 50, 800); // 0 overlap -> filtered
Mentor evan = new Mentor("evan", Set.of("java", "system-design"), 47, 5000); // over budget -> filtered
Mentor finn = new Mentor("finn", Set.of("system-design"), 30, 900); // under rating bar -> filtered
List<Mentor> pool = List.of(amir, bina, cory, dina, evan, finn);
// want java + system-design, budget 2000, at least 4.0 stars
Query q = new Query(Set.of("java", "system-design"), 2000, 40);
List<String> ranked = matcher.match(pool, q);
// dina (no overlap), evan (over budget), finn (under rating) are all filtered out
assertTrue(ranked.equals(List.of("amir", "bina", "cory")), "filtered + ranked: " + ranked);
// amir and bina both cover 2 skills; amir's higher rating ranks him first
assertTrue(matcher.score(amir, q) > matcher.score(bina, q), "equal overlap -> rating breaks the tie");
// cory covers only 1 skill, so despite the best rating he ranks below the 2-skill mentors
assertTrue(matcher.score(bina, q) > matcher.score(cory, q), "more skill overlap beats a higher rating");
// tighten the budget: only the cheapest qualifying mentors remain
Query tight = new Query(Set.of("java", "system-design"), 1100, 40);
assertTrue(matcher.match(pool, tight).equals(List.of("cory")), "tighter budget filters amir and bina");
// raise the bar so high nobody qualifies
Query strict = new Query(Set.of("java", "system-design"), 2000, 50);
assertTrue(matcher.match(pool, strict).isEmpty(), "an impossible rating bar returns no matches");
// a deterministic tie: two identical-score mentors break by id
Mentor zeb = new Mentor("zeb", Set.of("java", "system-design"), 45, 1200); // identical score to bina
List<String> tie = matcher.match(List.of(bina, zeb), q);
assertTrue(tie.equals(List.of("bina", "zeb")), "identical scores break by id ascending");
System.out.println("ALL MENTOR MATCH ASSERTIONS PASSED");
}
static void assertTrue(boolean cond, String msg) { if (!cond) throw new AssertionError(msg); }
}Step 7 — Scaling the match
- Pre-filter with an index → don't scan every mentor; an inverted index of
skill → mentorsnarrows to candidates who share at least one skill before scoring. - Score only candidates → the expensive ranking runs over the small filtered set, not the whole catalog.
- Shard by skill → mentor profiles partition by primary skill/domain, so a query touches only relevant shards.
- Cache hot queries → popular goal+budget combinations cache their ranked results; profiles change slowly.
The headline: index to filter cheaply, score only the survivors, and cache popular queries — the catalog can grow without the per-query cost growing with it.
Step 8 — When a piece fails: designing for failure
- No mentor matches the query → return an empty, honest result (as the code does) with suggestions to relax a constraint — never pad with off-topic mentors.
- A profile is stale (rating/price changed) → the match service reads from the profile store; a cached result is invalidated on profile update, so a mentee never books at a wrong price.
- The match service is down → it's stateless and read-only, so requests fail over to another replica; nothing is lost because it owns no state.
- A mentor games the score → because the score is transparent and overlap-dominated, gaming requires actually listing relevant skills; abuse (fake skills) is caught by review, and the formula stays explainable.
The interview corner
- "How do you match mentees to mentors?" Two stages: hard filters remove the unqualified (budget, rating, no overlapping skill), then a soft score ranks the rest. Filter then rank — never rank then hope.
- "Why weight overlap above rating?" Domain fit is the point of a mentor; a 5-star expert in the wrong topic is useless. Overlap ×100 guarantees fit dominates, rating only breaks ties within a tier.
- "How do you keep results deterministic?" A fixed additive score plus an id tie-break, so equal-scoring mentors always order the same way.
- "How does it scale?" Pre-filter via a
skill → mentorsindex, score only the candidates, shard profiles by skill, cache popular queries. - "How is this different from booking?" This is discovery (filter + rank); the slot-lock-pay-confirm booking is a separate stage — the Topmate flow.
Where to go from here
- The filter-then-rank shape is the same as LinkedIn's people search and Tinder's deck; the booking that follows is the Topmate engine.
- New to system design? The rookie's guide to HLD walks the method this article follows.