Functional Scope (In-Scope)
- Dynamic Elo Rating Adjustments: Calculates post-swipe rating modifications for both swipers and swipees based on expected outcomes.
- Glicko-1 Rating Deviations: Models rating uncertainty (RD) and integrates Glicko expected scores over dynamic match periods.
- Time-Based Score Uncertainty Decay: Increases rating deviation (RD) exponentially for inactive profiles, reflecting higher uncertainty.
- Blended Candidate Recommendation Sorters: Balances Elo compatibility differences with cold-start exploration boosts for high-RD new users.
Explicit Boundaries (Out-of-Scope)
- Real-time Profile Chat Channels: Focuses strictly on rating calculations and ranking recommendations.
- Geo-Spatial R-Tree Indices: Relies on pre-filtered candidate lists to focus on algorithmic scoring logic.
Production reference implementations demonstrating ELO calculators, Glicko-1 formulas, rating deviations, and candidate ranking sorters in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
enum SwipeResult {
LIKE, PASS
}
class UserScore {
private final String userId;
private volatile double eloRating;
private volatile double glickoRating;
private volatile double ratingDeviation; // RD (uncertainty)
private volatile long lastActiveAt;
public UserScore(String userId, double initialRating, double initialRD) {
this.userId = userId;
this.eloRating = initialRating;
this.glickoRating = initialRating;
this.ratingDeviation = initialRD;
this.lastActiveAt = System.currentTimeMillis();
}
public String getUserId() { return userId; }
public double getEloRating() { return eloRating; }
public void setEloRating(double eloRating) { this.eloRating = eloRating; }
public double getGlickoRating() { return glickoRating; }
public void setGlickoRating(double glickoRating) { this.glickoRating = glickoRating; }
public double getRatingDeviation() { return ratingDeviation; }
public void setRatingDeviation(double ratingDeviation) { this.ratingDeviation = ratingDeviation; }
public long getLastActiveAt() { return lastActiveAt; }
public void setLastActiveAt(long lastActiveAt) { this.lastActiveAt = lastActiveAt; }
}
class SwipeOutcome {
private final String swiperId;
private final String swipeeId;
private final SwipeResult result;
private final long timestampMs;
public SwipeOutcome(String swiperId, String swipeeId, SwipeResult result) {
this.swiperId = swiperId;
this.swipeeId = swipeeId;
this.result = result;
this.timestampMs = System.currentTimeMillis();
}
public String getSwiperId() { return swiperId; }
public String getSwipeeId() { return swipeeId; }
public SwipeResult getResult() { return result; }
public long getTimestampMs() { return timestampMs; }
}
class EloUpdater {
private static final double DEFAULT_K = 32.0;
private static final double NEW_USER_K = 64.0;
public static void updateRatings(UserScore swiper, UserScore swipee, SwipeResult result) {
double ratingA = swiper.getEloRating();
double ratingB = swipee.getEloRating();
// 1. Calculate expected probability of Swiper liking Swipee
double expectedA = 1.0 / (1.0 + Math.pow(10.0, (ratingB - ratingA) / 400.0));
double expectedB = 1.0 - expectedA;
// Swiper gets rating bump if they choose to LIKE (1.0) vs PASS (0.0)
double outcomeA = (result == SwipeResult.LIKE) ? 1.0 : 0.0;
double outcomeB = 1.0 - outcomeA; // Complementary score
// High K-factor applied for cold-start new users
double kSwiper = (swiper.getRatingDeviation() > 150.0) ? NEW_USER_K : DEFAULT_K;
double kSwipee = (swipee.getRatingDeviation() > 150.0) ? NEW_USER_K : DEFAULT_K;
swiper.setEloRating(ratingA + kSwiper * (outcomeA - expectedA));
swipee.setEloRating(ratingB + kSwipee * (outcomeB - expectedB));
swiper.setLastActiveAt(System.currentTimeMillis());
swipee.setLastActiveAt(System.currentTimeMillis());
}
}
class GlickoUpdater {
private static final double Q = Math.log(10.0) / 400.0;
private static final double PI_SQR = Math.PI * Math.PI;
private static double g(double rd) {
return 1.0 / Math.sqrt(1.0 + 3.0 * Q * Q * rd * rd / PI_SQR);
}
private static double expectedScore(double r, double rOpponent, double gRD_Opponent) {
return 1.0 / (1.0 + Math.pow(10.0, -gRD_Opponent * (r - rOpponent) / 400.0));
}
// Dynamic Glicko-1 Batch Rating Update
public static void updateGlicko(UserScore user, List<UserScore> opponents, List<SwipeResult> outcomes) {
double r = user.getGlickoRating();
double rd = user.getRatingDeviation();
int matches = opponents.size();
if (matches == 0) return;
double dSqrSum = 0.0;
double ratingDeltaSum = 0.0;
for (int i = 0; i < matches; i++) {
UserScore opp = opponents.get(i);
double rOpp = opp.getGlickoRating();
double rdOpp = opp.getRatingDeviation();
double gRD = g(rdOpp);
double expected = expectedScore(r, rOpp, gRD);
double outcomeVal = (outcomes.get(i) == SwipeResult.LIKE) ? 1.0 : 0.0;
dSqrSum += gRD * gRD * expected * (1.0 - expected);
ratingDeltaSum += gRD * (outcomeVal - expected);
}
double dSqr = 1.0 / (Q * Q * dSqrSum);
double newRD = Math.sqrt(1.0 / ((1.0 / (rd * rd)) + (1.0 / dSqr)));
double newR = r + (Q / ((1.0 / (rd * rd)) + (1.0 / dSqr))) * ratingDeltaSum;
user.setGlickoRating(newR);
user.setRatingDeviation(newRD);
user.setLastActiveAt(System.currentTimeMillis());
}
}
class ScoreDecay {
private static final double DECAY_CONSTANT_C = 1.5; // Controls uncertainty widening speed
private static final double MAX_RD = 350.0; // Ceiling boundary
// Increment RD (uncertainty) based on inactivity days
public static void decayRatingDeviation(UserScore user) {
long now = System.currentTimeMillis();
long inactiveDurationMs = now - user.getLastActiveAt();
double daysInactive = inactiveDurationMs / (24.0 * 3600.0 * 1000.0);
if (daysInactive >= 7.0) { // Widen uncertainty after 7 days
double currentRD = user.getRatingDeviation();
double newRD = Math.sqrt(currentRD * currentRD + DECAY_CONSTANT_C * DECAY_CONSTANT_C * daysInactive);
user.setRatingDeviation(Math.min(newRD, MAX_RD));
}
}
}
class CandidateSorter {
// Blends Elo compatibility, proximity weights, and cold-start exploration RD boosts
public static List<UserScore> rankCandidates(UserScore swiper, List<UserScore> candidates) {
List<UserScore> ranked = new ArrayList<>(candidates);
ranked.sort((c1, c2) -> {
double score1 = calculateRankScore(swiper, c1);
double score2 = calculateRankScore(swiper, c2);
return Double.compare(score2, score1); // Descending order
});
return ranked;
}
private static double calculateRankScore(UserScore swiper, UserScore candidate) {
double eloDiff = Math.abs(swiper.getEloRating() - candidate.getEloRating());
// Base compatibility decreases as ELO difference increases
double compatibility = Math.max(0.0, 1000.0 - eloDiff);
// Cold-Start exploration boost: High RD candidates get ranked higher to gather data
double explorationBoost = candidate.getRatingDeviation() * 0.5;
return compatibility + explorationBoost;
}
}
class TinderRankingService {
private final ConcurrentHashMap<String, UserScore> userScores = new ConcurrentHashMap<>();
public void registerUser(String userId) {
// Cold-start settings: Standard Elo 1500, High RD 350 (Maximum uncertainty)
userScores.put(userId, new UserScore(userId, 1500.0, 350.0));
}
public void recordSwipe(String swiperId, String swipeeId, SwipeResult result) {
UserScore swiper = userScores.get(swiperId);
UserScore swipee = userScores.get(swipeeId);
if (swiper == null || swipee == null) return;
// Apply decay checks for inactivity
ScoreDecay.decayRatingDeviation(swiper);
ScoreDecay.decayRatingDeviation(swipee);
// Perform ELO updates
EloUpdater.updateRatings(swiper, swipee, result);
}
public List<UserScore> getRecommendations(String swiperId, List<String> candidateIds) {
UserScore swiper = userScores.get(swiperId);
if (swiper == null) return Collections.emptyList();
List<UserScore> candidates = new ArrayList<>();
for (String id : candidateIds) {
UserScore score = userScores.get(id);
if (score != null) {
ScoreDecay.decayRatingDeviation(score); // Decay check before ranking
candidates.add(score);
}
}
return CandidateSorter.rankCandidates(swiper, candidates);
}
public UserScore getUserScore(String userId) {
return userScores.get(userId);
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== JAVA TINDER ADVANCED SIMULATION ===");
TinderRankingService service = new TinderRankingService();
service.registerUser("alice");
service.registerUser("bob");
service.registerUser("charlie");
System.out.println("Alice initial Elo: " + service.getUserScore("alice").getEloRating());
// Alice swipes LIKE on Bob
service.recordSwipe("alice", "bob", SwipeResult.LIKE);
System.out.println("Alice Elo after swipe: " + service.getUserScore("alice").getEloRating());
System.out.println("Bob Elo after swipe: " + service.getUserScore("bob").getEloRating());
List<UserScore> recs = service.getRecommendations("alice", Arrays.asList("bob", "charlie"));
System.out.println("Recommendations for Alice:");
for (UserScore u : recs) {
System.out.println(" - User: " + u.getUserId() + " | Elo: " + u.getEloRating());
}
System.out.println("=== END OF JAVA SIMULATION ===");
}
}