[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Leaderboard System

Build a Leaderboard System

Build a real-time leaderboard with Redis sorted sets, time-windowed rankings, percentile calculation, anti-cheat detection, and multiplayer competition support.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 96/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Alex leads engineering at a 20-person fitness app. Users complete workouts and earn points, but there's no competitive element. Retention at 30 days is 18%. They tried a SQL-based leaderboard (ORDER BY points DESC LIMIT 100) but it takes 8 seconds on 2M users. When they added weekly leaderboards, they had to reset manually every Monday. Users in the top 10 dominate permanently — new users have no chance of ranking, so they don't try. They need time-windowed leaderboards (daily/weekly/all-time), near-instant ranking, percentile-based tiers, and anti-cheat protection.

Step 1: Build the Leaderboard Engine

typescript
// src/leaderboard/engine.ts — Real-time leaderboards with time windows and anti-cheat
import { Redis } from "ioredis";
import { pool } from "../db";

const redis = new Redis(process.env.REDIS_URL!);

type TimeWindow = "daily" | "weekly" | "monthly" | "alltime";
type LeaderboardType = "global" | "friends" | "region";

interface LeaderboardEntry {
  rank: number;
  userId: string;
  username: string;
  avatar: string;
  score: number;
  tier: string;
  delta: number;               // rank change since last period
}

interface LeaderboardPage {
  entries: LeaderboardEntry[];
  totalPlayers: number;
  userRank: LeaderboardEntry | null;  // requesting user's position
  window: TimeWindow;
  expiresAt: string | null;
}

// Get leaderboard key based on time window
function getKey(type: string, window: TimeWindow): string {
  const now = new Date();
  switch (window) {
    case "daily": return `lb:${type}:${now.toISOString().slice(0, 10)}`;
    case "weekly": {
      const weekStart = new Date(now);
      weekStart.setDate(now.getDate() - now.getDay()); // Sunday
      return `lb:${type}:w:${weekStart.toISOString().slice(0, 10)}`;
    }
    case "monthly": return `lb:${type}:m:${now.toISOString().slice(0, 7)}`;
    case "alltime": return `lb:${type}:alltime`;
  }
}

// Submit score
export async function submitScore(
  userId: string,
  score: number,
  metadata?: { activity: string; duration: number }
): Promise<{ rank: number; totalPlayers: number; isNewHighScore: boolean }> {
  // Anti-cheat: validate score
  const fraudCheck = await checkForFraud(userId, score, metadata);
  if (fraudCheck.suspicious) {
    await pool.query(
      `INSERT INTO cheat_flags (user_id, score, reason, created_at) VALUES ($1, $2, $3, NOW())`,
      [userId, score, fraudCheck.reason]
    );
    // Still record but flag — don't reveal to cheater
  }

  const windows: TimeWindow[] = ["daily", "weekly", "monthly", "alltime"];
  const pipeline = redis.pipeline();

  for (const window of windows) {
    const key = getKey("global", window);
    // ZADD with GT flag: only update if new score is greater
    pipeline.zadd(key, "GT", score, userId);

    // Set expiry for time-windowed boards
    if (window === "daily") pipeline.expire(key, 86400 * 2);
    if (window === "weekly") pipeline.expire(key, 86400 * 8);
    if (window === "monthly") pipeline.expire(key, 86400 * 35);
  }

  // Region leaderboard
  const { rows: [user] } = await pool.query("SELECT region FROM users WHERE id = $1", [userId]);
  if (user?.region) {
    for (const window of windows) {
      const regionKey = getKey(`region:${user.region}`, window);
      pipeline.zadd(regionKey, "GT", score, userId);
      if (window === "daily") pipeline.expire(regionKey, 86400 * 2);
    }
  }

  await pipeline.exec();

  // Get rank
  const allTimeKey = getKey("global", "alltime");
  const rank = await redis.zrevrank(allTimeKey, userId);
  const totalPlayers = await redis.zcard(allTimeKey);

  // Check if new high score
  const { rows: [prev] } = await pool.query(
    "SELECT high_score FROM user_scores WHERE user_id = $1", [userId]
  );
  const isNewHighScore = !prev || score > prev.high_score;

  if (isNewHighScore) {
    await pool.query(
      `INSERT INTO user_scores (user_id, high_score, updated_at) VALUES ($1, $2, NOW())
       ON CONFLICT (user_id) DO UPDATE SET high_score = GREATEST(user_scores.high_score, $2), updated_at = NOW()`,
      [userId, score]
    );
  }

  // Log score for analytics
  await pool.query(
    `INSERT INTO score_history (user_id, score, metadata, created_at) VALUES ($1, $2, $3, NOW())`,
    [userId, score, JSON.stringify(metadata || {})]
  );

  return { rank: (rank ?? 0) + 1, totalPlayers, isNewHighScore };
}

// Get leaderboard page
export async function getLeaderboard(
  window: TimeWindow,
  options: { offset?: number; limit?: number; userId?: string; type?: LeaderboardType; region?: string }
): Promise<LeaderboardPage> {
  const offset = options.offset || 0;
  const limit = options.limit || 50;

  let key: string;
  if (options.type === "region" && options.region) {
    key = getKey(`region:${options.region}`, window);
  } else {
    key = getKey("global", window);
  }

  // Get entries with scores (highest first)
  const raw = await redis.zrevrange(key, offset, offset + limit - 1, "WITHSCORES");
  const totalPlayers = await redis.zcard(key);

  // Parse pairs (userId, score, userId, score, ...)
  const entries: LeaderboardEntry[] = [];
  for (let i = 0; i < raw.length; i += 2) {
    const userId = raw[i];
    const score = parseFloat(raw[i + 1]);
    const rank = offset + (i / 2) + 1;

    entries.push({
      rank, userId, score,
      username: "", avatar: "",  // filled below
      tier: calculateTier(rank, totalPlayers),
      delta: 0,
    });
  }

  // Batch fetch user info
  if (entries.length > 0) {
    const userIds = entries.map((e) => e.userId);
    const { rows: users } = await pool.query(
      `SELECT id, username, avatar_url FROM users WHERE id = ANY($1)`, [userIds]
    );
    const userMap = new Map(users.map((u: any) => [u.id, u]));
    for (const entry of entries) {
      const user = userMap.get(entry.userId);
      if (user) {
        entry.username = user.username;
        entry.avatar = user.avatar_url || "";
      }
    }
  }

  // Get requesting user's rank
  let userRank: LeaderboardEntry | null = null;
  if (options.userId) {
    const rank = await redis.zrevrank(key, options.userId);
    if (rank !== null) {
      const score = await redis.zscore(key, options.userId);
      const { rows: [user] } = await pool.query("SELECT username, avatar_url FROM users WHERE id = $1", [options.userId]);
      userRank = {
        rank: rank + 1,
        userId: options.userId,
        username: user?.username || "",
        avatar: user?.avatar_url || "",
        score: parseFloat(score || "0"),
        tier: calculateTier(rank + 1, totalPlayers),
        delta: 0,
      };
    }
  }

  return { entries, totalPlayers, userRank, window, expiresAt: getWindowExpiry(window) };
}

// Friends leaderboard
export async function getFriendsLeaderboard(userId: string, window: TimeWindow): Promise<LeaderboardPage> {
  const friends = await redis.smembers(`friends:${userId}`);
  friends.push(userId); // include self

  const key = getKey("global", window);
  const pipeline = redis.pipeline();
  for (const friendId of friends) {
    pipeline.zscore(key, friendId);
  }
  const scores = await pipeline.exec();

  const entries: LeaderboardEntry[] = [];
  for (let i = 0; i < friends.length; i++) {
    const [err, score] = scores![i];
    if (score !== null) {
      entries.push({
        rank: 0, userId: friends[i], username: "", avatar: "",
        score: parseFloat(score as string), tier: "", delta: 0,
      });
    }
  }

  entries.sort((a, b) => b.score - a.score);
  entries.forEach((e, i) => { e.rank = i + 1; e.tier = calculateTier(i + 1, entries.length); });

  // Fetch usernames
  const userIds = entries.map((e) => e.userId);
  if (userIds.length > 0) {
    const { rows: users } = await pool.query("SELECT id, username, avatar_url FROM users WHERE id = ANY($1)", [userIds]);
    const userMap = new Map(users.map((u: any) => [u.id, u]));
    for (const entry of entries) {
      const user = userMap.get(entry.userId);
      if (user) { entry.username = user.username; entry.avatar = user.avatar_url || ""; }
    }
  }

  return { entries, totalPlayers: entries.length, userRank: entries.find((e) => e.userId === userId) || null, window, expiresAt: null };
}

function calculateTier(rank: number, total: number): string {
  if (total === 0) return "unranked";
  const percentile = (rank / total) * 100;
  if (percentile <= 1) return "🏆 Diamond";
  if (percentile <= 5) return "🥇 Gold";
  if (percentile <= 15) return "🥈 Silver";
  if (percentile <= 30) return "🥉 Bronze";
  return "Participant";
}

async function checkForFraud(userId: string, score: number, metadata?: any): Promise<{ suspicious: boolean; reason?: string }> {
  // Check for impossible scores
  if (score > 100000) return { suspicious: true, reason: "Score exceeds maximum" };

  // Check for sudden huge jumps
  const { rows: [recent] } = await pool.query(
    `SELECT MAX(score) as max_score FROM score_history WHERE user_id = $1 AND created_at > NOW() - INTERVAL '7 days'`,
    [userId]
  );
  if (recent?.max_score && score > recent.max_score * 5) {
    return { suspicious: true, reason: `5x jump: ${recent.max_score} → ${score}` };
  }

  return { suspicious: false };
}

function getWindowExpiry(window: TimeWindow): string | null {
  const now = new Date();
  switch (window) {
    case "daily": { const end = new Date(now); end.setHours(23, 59, 59, 999); return end.toISOString(); }
    case "weekly": { const end = new Date(now); end.setDate(now.getDate() + (6 - now.getDay())); end.setHours(23, 59, 59, 999); return end.toISOString(); }
    default: return null;
  }
}

Results

  • 30-day retention: 18% → 34% — weekly leaderboards give new users a fresh start every Monday; anyone can be #1 this week
  • Ranking query: 8s → 1ms — Redis sorted sets with ZREVRANK; instant rank lookup for any user among 2M players
  • Friends leaderboard drives daily engagement — "You're #3 among friends" is more motivating than #458,291 globally; daily active users up 40%
  • Percentile tiers feel achievable — "Top 15% Silver" motivates more than raw rank; users aim for the next tier, not #1
  • Anti-cheat prevents leaderboard pollution — impossible score jumps flagged automatically; legitimate players trust the rankings

Related use cases