Debugging challengeIntermediate

The comparator that silenced the compiler

Scenario

A leaderboard library exposes sortBy(items, key) and maxBy(items, key), generic over any object type. Sorting players by score works perfectly and is covered by tests. Sorting by name returns the players in their original insertion order — no error, no warning — and maxBy(players, 'name') always returns whichever player happens to be first. The code compiles clean under strict: true. Find the bug, and explain why the compiler *did* try to prevent it.

1interface Player { name: string; score: number; rank: number }
2
3function sortBy<T, K extends keyof T>(items: T[], key: K): T[] {
4 // First attempt was `a[key] - b[key]`, which tsc rejected:
5 // error TS2362: The left-hand side of an arithmetic operation must be of
6 // type 'any', 'number', 'bigint' or an enum type.
7 // "Fixed" by coercing both sides:
8 return [...items].sort((a, b) => Number(a[key]) - Number(b[key]));
9}
10
11function maxBy<T, K extends keyof T>(items: T[], key: K): T | undefined {
12 let best: T | undefined;
13 for (const it of items) {
14 if (best === undefined || Number(it[key]) > Number(best[key])) best = it;
15 }
16 return best;
17}
18
19const players: Player[] = [
20 { name: 'zoe', score: 12, rank: 3 },
21 { name: 'amir', score: 98, rank: 1 },
22 { name: 'lena', score: 55, rank: 2 },
23];
24
25console.log(sortBy(players, 'score').map((p) => p.name)); // ['zoe', 'lena', 'amir'] — works
26console.log(sortBy(players, 'name').map((p) => p.name)); // ['zoe', 'amir', 'lena'] — untouched
27console.log(maxBy(players, 'name')?.name); // 'zoe' — looks right by luck…
28console.log(maxBy(players.slice(1), 'name')?.name); // 'amir' — expected 'lena': always the first element

Your task

  1. What does Number('amir') evaluate to, and what does the comparator return when both operands are player names? What does the sort algorithm do with that return value?
  2. The original a[key] - b[key] was a compile error. What was TS2362 actually telling the author about T[K], and why did wrapping both sides in Number(...) make the error disappear without making the code correct?
  3. Explain why maxBy(players, 'name') returns the *first* element rather than a random one.
  4. Rewrite the API with a selector function (item: T) => number so the bad call fails to compile. Which call sites break, and why is that the point?
  5. Keep a key-based API too: write a NumericKeys<T> conditional type so that sortBy(players, 'name') is rejected at compile time while sortBy(players, 'score') still works.
  6. State the complexity and whether the fixed sort is stable.
DebuggingSystematic ReasoningImplementation

Work it out

Write your analysis before revealing anything. The self-check below compares it against what a strong answer contains.

Reveal

Progressive — each section builds on the previous one.

The bug
Why it happens
The fix
Edge cases
Complexity

Self-check

Tick what your analysis covered. Be honest — this feeds your readiness profile.

0/7

Related concepts