Machine Coding Problem

Tinder Advanced (Elo/Glicko)

macoAllsocialelo/glicko-scoring
Commonly Asked By:Match GroupTinderBumble

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 ===");
    }
}