Sanjaay Gowda T S
Interview Prep

THE CODING LAB / ORIGINAL PRACTICE

Make your reasoning
runnable.

Write an approach, run the visible tests, and inspect the actual and expected results. Solutions are public so you can compare and learn.

JavaScript is required to run code. Python and SQL download their browser runtimes on first use. All prompts, test cases and solutions are readable below.

Switching exercises or languages keeps separate drafts in this page. Reset restores the selected starter; Undo reset recovers your previous draft.

YOUR CURRENT EXERCISE

Find the first two-sum pair

Input is {nums: integer[], target: integer}. Return indices [i,j] with i < j and nums[i] + nums[j] = target. Choose the smallest possible j; among pairs with that j, choose the smallest i. Return [] when no pair exists. Numbers and sums are within the JavaScript safe-integer range. Do not sort or modify nums.

Use a hash map while preserving a precise tie-breaking rule.

Suggested approach complexity: Expected O(n) time and O(n) additional space with hash-map operations; worst-case hash behavior is implementation dependent.

Read all prompts, tests & solutions

Enable JavaScript to run the exercise. All test cases and solutions remain readable below.

Edit the starter code, then run it against the public tests. Stop ends the current run.

Download your code, custom test input and expected result before leaving. Files are created on your device and contain your text; nothing is uploaded. The browser may warn about undownloaded edits. A download request cannot confirm that you saved the file.

Test input

Optional: compare your own edge case with an expected result. Your check is separate from the public score, and your expectation may be incorrect. Without an expected value, the custom result is ungraded. Maximum 32 KiB, 32 nested levels and 10,000 values. All values stay in this page unless you download them.

Standard output

No output yet.

Test results

Run the exercise to compare actual and expected output.

Show solution
function solve({nums, target}) {
  const first = new Map();
  for (let j = 0; j < nums.length; j++) {
    const needed = target - nums[j];
    if (first.has(needed)) return [first.get(needed), j];
    if (!first.has(nums[j])) first.set(nums[j], j);
  }
  return [];
}

THE PUBLIC PRACTICE LIBRARY

Prompts. Tests. Solutions.

24 original exercises. Read an approach, inspect the test inputs, or load a task into the editor.

Find the first two-sum pairEasy · JavaScript / Python

Input is {nums: integer[], target: integer}. Return indices [i,j] with i < j and nums[i] + nums[j] = target. Choose the smallest possible j; among pairs with that j, choose the smallest i. Return [] when no pair exists. Numbers and sums are within the JavaScript safe-integer range. Do not sort or modify nums.

Use a hash map while preserving a precise tie-breaking rule.

Public test cases

Typical pair

Input

{
  "nums": [
    2,
    7,
    11,
    15
  ],
  "target": 9
}

Expected output

[
  0,
  1
]
Empty input

Input

{
  "nums": [],
  "target": 0
}

Expected output

[]
Duplicate tie

Input

{
  "nums": [
    3,
    3,
    3
  ],
  "target": 6
}

Expected output

[
  0,
  1
]
Negative numbers

Input

{
  "nums": [
    -4,
    2,
    0,
    6
  ],
  "target": 2
}

Expected output

[
  1,
  2
]
No pair

Input

{
  "nums": [
    1,
    2
  ],
  "target": 9
}

Expected output

[]

Approach

Before index j, the map contains the first index of each earlier value. Look up the complement before inserting the current value, so an element cannot pair with itself. Keeping the first index, rather than overwriting it, implements the i tie-break. A sorting solution must do additional work to preserve original indices and this exact ordering.

Complexity: Expected O(n) time and O(n) additional space with hash-map operations; worst-case hash behavior is implementation dependent.

JavaScript solution
function solve({nums, target}) {
  const first = new Map();
  for (let j = 0; j < nums.length; j++) {
    const needed = target - nums[j];
    if (first.has(needed)) return [first.get(needed), j];
    if (!first.has(nums[j])) first.set(nums[j], j);
  }
  return [];
}
Python solution
def solve(input):
    first = {}
    for j, value in enumerate(input['nums']):
        needed = input['target'] - value
        if needed in first:
            return [first[needed], j]
        if value not in first:
            first[value] = j
    return []
Load this exercise in the editor
Validate nested delimitersEasy · JavaScript / Python

Input is an ASCII string. Return true when its (), [], and {} delimiters are balanced and properly nested. Ignore every other character. An empty string is balanced. A closing delimiter with no matching open delimiter must fail immediately.

Practice stack invariants and early rejection.

Public test cases

Nested expression

Input

"a{b[c(d)]}"

Expected output

true
Empty string

Input

""

Expected output

true
Crossed delimiters

Input

"([)]"

Expected output

false
Premature close

Input

")("

Expected output

false
Unclosed open

Input

"text("

Expected output

false

Approach

The stack holds exactly the unmatched openings in encounter order. Each close must match its most recent opening. Equal counts alone do not establish nesting: ([)] has equal counts and still fails. Ignore ordinary characters without changing the stack, and check for leftover openings after the loop.

Complexity: O(n) time and O(n) additional space in the worst case.

JavaScript solution
function solve(input) {
  const stack = [], pairs = {')':'(', ']':'[', '}':'{'};
  for (const ch of input) {
    if ('([{'.includes(ch)) stack.push(ch);
    else if (Object.hasOwn(pairs, ch) && stack.pop() !== pairs[ch]) return false;
  }
  return stack.length === 0;
}
Python solution
def solve(input):
    stack = []
    pairs = {')':'(', ']':'[', '}':'{'}
    for ch in input:
        if ch in '([{':
            stack.append(ch)
        elif ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False
    return not stack
Load this exercise in the editor
Merge inclusive intervalsMedium · JavaScript / Python

Input is {intervals: [[start,end], ...]} with integer start <= end. Intervals use inclusive endpoints, and intervals sharing an endpoint overlap. Return merged intervals sorted by start, then end. Do not mutate the input. Adjacent intervals [1,2] and [3,4] do not overlap under this rule.

Turn an ordering step into a simple scanning invariant.

Public test cases

Overlapping unsorted

Input

{
  "intervals": [
    [
      8,
      10
    ],
    [
      1,
      3
    ],
    [
      2,
      6
    ],
    [
      15,
      18
    ]
  ]
}

Expected output

[
  [
    1,
    6
  ],
  [
    8,
    10
  ],
  [
    15,
    18
  ]
]
Empty

Input

{
  "intervals": []
}

Expected output

[]
Shared endpoints and duplicates

Input

{
  "intervals": [
    [
      1,
      2
    ],
    [
      2,
      4
    ],
    [
      1,
      2
    ]
  ]
}

Expected output

[
  [
    1,
    4
  ]
]
Adjacent integers

Input

{
  "intervals": [
    [
      1,
      2
    ],
    [
      3,
      4
    ]
  ]
}

Expected output

[
  [
    1,
    2
  ],
  [
    3,
    4
  ]
]
Negative and contained

Input

{
  "intervals": [
    [
      -5,
      0
    ],
    [
      -4,
      -2
    ],
    [
      0,
      0
    ]
  ]
}

Expected output

[
  [
    -5,
    0
  ]
]

Approach

After sorting, only the final merged interval can overlap the next interval. Extend that endpoint with max rather than blindly assigning the new end: a contained interval must not shrink the merged result. Copy input pairs in JavaScript because the result is updated in place; Python constructs new output pairs.

Complexity: O(n log n) time for sorting and O(n) additional space for the ordered copy and result.

JavaScript solution
function solve({intervals}) {
  const ordered = intervals.map(pair => [...pair]).sort((a,b) => a[0]-b[0] || a[1]-b[1]);
  const merged = [];
  for (const pair of ordered) {
    const last = merged[merged.length-1];
    if (last && pair[0] <= last[1]) last[1] = Math.max(last[1], pair[1]);
    else merged.push(pair);
  }
  return merged;
}
Python solution
def solve(input):
    merged = []
    for start, end in sorted(input['intervals']):
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged
Load this exercise in the editor
Find a longest unique windowMedium · JavaScript / Python

Input is an ASCII string. Return the length of its longest contiguous substring with no repeated character. Return 0 for the empty string. This exercise deliberately uses ASCII so JavaScript and Python agree about character indexing.

Maintain a sliding window with last-seen indices.

Public test cases

Repeated blocks

Input

"abcabcbb"

Expected output

3
Empty

Input

""

Expected output

0
One repeated character

Input

"bbbb"

Expected output

1
Noncontiguous temptation

Input

"pwwkew"

Expected output

3
Do not move left backward

Input

"abba"

Expected output

2

Approach

The active window from left through right contains no duplicate. A repeated character moves left beyond its prior position only when that position is still inside the window. max prevents moving left backward, the common abba bug. A subsequence is not a substring: characters cannot be skipped to lengthen the answer.

Complexity: O(n) expected time and O(min(n, alphabet size)) additional space.

JavaScript solution
function solve(input) {
  const last = new Map();
  let left = 0, best = 0;
  for (let right = 0; right < input.length; right++) {
    if (last.has(input[right])) left = Math.max(left, last.get(input[right])+1);
    last.set(input[right], right);
    best = Math.max(best, right-left+1);
  }
  return best;
}
Python solution
def solve(input):
    last, left, best = {}, 0, 0
    for right, ch in enumerate(input):
        if ch in last:
            left = max(left, last[ch] + 1)
        last[ch] = right
        best = max(best, right - left + 1)
    return best
Load this exercise in the editor
Locate the first matching indexEasy · JavaScript / Python

Input is {nums: sorted nondecreasing integer[], target: integer}. Return the first index containing target, or -1 when absent. The empty array is valid. Do not use a linear scan or modify nums. A target below the minimum or above the maximum is absent; duplicates must return the leftmost index.

Use lower-bound search to handle duplicates reliably.

Public test cases

Duplicates

Input

{
  "nums": [
    1,
    2,
    2,
    2,
    5
  ],
  "target": 2
}

Expected output

1
Empty

Input

{
  "nums": [],
  "target": 1
}

Expected output

-1
Last boundary

Input

{
  "nums": [
    -3,
    0,
    8
  ],
  "target": 8
}

Expected output

2
Missing insertion point

Input

{
  "nums": [
    1,
    3,
    5
  ],
  "target": 4
}

Expected output

-1
First boundary

Input

{
  "nums": [
    4,
    4
  ],
  "target": 4
}

Expected output

0

Approach

The half-open search range [low,high) shrinks until low is the first position whose value is at least target. Equality keeps searching left. A final equality check distinguishes a found value from an insertion point. high starts at length, so the algorithm also handles empty input without reading an element.

Complexity: O(log n) time and O(1) additional space.

JavaScript solution
function solve({nums, target}) {
  let low = 0, high = nums.length;
  while (low < high) {
    const mid = low + Math.floor((high-low)/2);
    if (nums[mid] < target) low = mid+1;
    else high = mid;
  }
  return low < nums.length && nums[low] === target ? low : -1;
}
Python solution
def solve(input):
    nums, target = input['nums'], input['target']
    low, high = 0, len(nums)
    while low < high:
        mid = low + (high - low) // 2
        if nums[mid] < target:
            low = mid + 1
        else:
            high = mid
    return low if low < len(nums) and nums[low] == target else -1
Load this exercise in the editor
Find an unweighted grid routeMedium · JavaScript / Python

Input is {grid: rectangular array of 0 or 1}. Zero cells are walkable; ones are blocked. Return the minimum number of orthogonal moves from the top-left to the bottom-right. Return -1 for an empty grid, blocked endpoint, or unreachable destination. A single walkable cell requires 0 moves. Diagonal moves are not allowed.

Use breadth-first search when every move has equal cost.

Public test cases

Short route

Input

{
  "grid": [
    [
      0,
      0,
      0
    ],
    [
      1,
      1,
      0
    ]
  ]
}

Expected output

3
Empty

Input

{
  "grid": []
}

Expected output

-1
Single walkable cell

Input

{
  "grid": [
    [
      0
    ]
  ]
}

Expected output

0
Blocked endpoint

Input

{
  "grid": [
    [
      1
    ]
  ]
}

Expected output

-1
Disconnected

Input

{
  "grid": [
    [
      0,
      1
    ],
    [
      1,
      0
    ]
  ]
}

Expected output

-1

Approach

Breadth-first search visits cells by increasing move count. Mark a cell seen when it is enqueued so several parents cannot add it repeatedly. The JavaScript queue uses a head index instead of shift, avoiding repeated array moves. This shortest-path argument relies on equal move costs; weighted routes need a different algorithm.

Complexity: O(rows * columns) time and additional space.

JavaScript solution
function solve({grid}) {
  const rows = grid.length, cols = rows ? grid[0].length : 0;
  if (!rows || !cols || grid[0][0] || grid[rows-1][cols-1]) return -1;
  const queue = [[0,0,0]], seen = new Set(['0,0']);
  for (let head = 0; head < queue.length; head++) {
    const [r,c,d] = queue[head];
    if (r === rows-1 && c === cols-1) return d;
    for (const [dr,dc] of [[1,0],[-1,0],[0,1],[0,-1]]) {
      const nr = r+dr, nc = c+dc, key = nr+','+nc;
      if (nr>=0 && nr<rows && nc>=0 && nc<cols && grid[nr][nc]===0 && !seen.has(key)) {
        seen.add(key); queue.push([nr,nc,d+1]);
      }
    }
  }
  return -1;
}
Python solution
def solve(input):
    from collections import deque
    grid = input['grid']
    rows, cols = len(grid), len(grid[0]) if grid else 0
    if not rows or not cols or grid[0][0] or grid[-1][-1]:
        return -1
    queue, seen = deque([(0, 0, 0)]), {(0, 0)}
    while queue:
        r, c, distance = queue.popleft()
        if (r, c) == (rows - 1, cols - 1):
            return distance
        for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0 and (nr, nc) not in seen:
                seen.add((nr, nc))
                queue.append((nr, nc, distance + 1))
    return -1
Load this exercise in the editor
Order dependency tasks deterministicallyMedium · JavaScript / Python

Input is {tasks: distinct ASCII string IDs, edges: [[before,after], ...]}. Every edge references a listed task. Return the lexicographically smallest valid topological ordering. Duplicate edges count once. Return [] if the graph has a cycle; an empty task list also returns []. Standard ASCII lexicographic order applies, not locale-aware sorting.

Combine indegrees with a reproducible tie-break.

Public test cases

Ready task tie

Input

{
  "tasks": [
    "test",
    "build",
    "lint"
  ],
  "edges": [
    [
      "build",
      "test"
    ]
  ]
}

Expected output

[
  "build",
  "lint",
  "test"
]
Empty graph

Input

{
  "tasks": [],
  "edges": []
}

Expected output

[]
Duplicate dependency

Input

{
  "tasks": [
    "a",
    "b",
    "c"
  ],
  "edges": [
    [
      "a",
      "b"
    ],
    [
      "a",
      "b"
    ]
  ]
}

Expected output

[
  "a",
  "b",
  "c"
]
Cycle

Input

{
  "tasks": [
    "a",
    "b"
  ],
  "edges": [
    [
      "a",
      "b"
    ],
    [
      "b",
      "a"
    ]
  ]
}

Expected output

[]

Approach

Kahn's algorithm removes a ready task and decreases each dependent indegree. Always choose the smallest ready ID to obtain the lexicographically smallest order. Deduplicate edges before counting indegrees, otherwise one dependency can falsely prevent completion. If fewer than all tasks are removed, a cycle remains. The two reference implementations deliberately show a simple sorted-array queue and a heap tradeoff.

Complexity: Python: O(E + V log V) expected time with a heap, O(V + E) space. JavaScript: O(E + V^2 log V) conservative worst-case bound from re-sorting and shifting the ready array; O(V + E) space.

JavaScript solution
function solve({tasks, edges}) {
  const outgoing = new Map(tasks.map(t => [t,new Set()]));
  const degree = new Map(tasks.map(t => [t,0]));
  for (const [a,b] of edges) if (!outgoing.get(a).has(b)) {
    outgoing.get(a).add(b); degree.set(b,degree.get(b)+1);
  }
  const ready = tasks.filter(t => degree.get(t)===0).sort(), answer = [];
  while (ready.length) {
    const task = ready.shift(); answer.push(task);
    for (const next of outgoing.get(task)) {
      degree.set(next,degree.get(next)-1);
      if (degree.get(next)===0) {ready.push(next); ready.sort();}
    }
  }
  return answer.length===tasks.length ? answer : [];
}
Python solution
def solve(input):
    import heapq
    outgoing = {task: set() for task in input['tasks']}
    degree = {task: 0 for task in input['tasks']}
    for before, after in input['edges']:
        if after not in outgoing[before]:
            outgoing[before].add(after)
            degree[after] += 1
    ready = [task for task in input['tasks'] if degree[task] == 0]
    heapq.heapify(ready)
    answer = []
    while ready:
        task = heapq.heappop(ready)
        answer.append(task)
        for next_task in outgoing[task]:
            degree[next_task] -= 1
            if degree[next_task] == 0:
                heapq.heappush(ready, next_task)
    return answer if len(answer) == len(input['tasks']) else []
Load this exercise in the editor
Report each sliding-window maximumMedium · JavaScript / Python

Input is {nums: integer[], k: positive integer}. Return one maximum for every contiguous window of exactly k elements. Empty input or k greater than the array length returns []. Equal maxima are allowed. Target linear time rather than sorting each window.

Maintain a monotonic deque of candidate indices.

Public test cases

Moving maxima

Input

{
  "nums": [
    1,
    3,
    -1,
    -3,
    5,
    3,
    6,
    7
  ],
  "k": 3
}

Expected output

[
  3,
  3,
  5,
  5,
  6,
  7
]
Empty

Input

{
  "nums": [],
  "k": 1
}

Expected output

[]
Duplicate maxima

Input

{
  "nums": [
    4,
    4,
    2,
    4
  ],
  "k": 2
}

Expected output

[
  4,
  4,
  4
]
Unit window

Input

{
  "nums": [
    -2,
    0,
    3
  ],
  "k": 1
}

Expected output

[
  -2,
  0,
  3
]
Window too large

Input

{
  "nums": [
    1,
    2
  ],
  "k": 3
}

Expected output

[]

Approach

Candidate indices remain in increasing index order and decreasing value order. Expire indices outside the window, then remove smaller or equal values from the back: they cannot beat the newer value in any later window. Each index enters and leaves at most once. The JavaScript head-index array retains expired storage, a useful distinction from a true deque.

Complexity: O(n) time. Python uses O(k) deque space plus output. JavaScript uses O(n) retained array space plus output; compacting the deque can recover an O(k) bound.

JavaScript solution
function solve({nums, k}) {
  if (!nums.length || k > nums.length) return [];
  const deque = [], result = []; let head = 0;
  for (let i=0; i<nums.length; i++) {
    while (head < deque.length && deque[head] <= i-k) head++;
    while (head < deque.length && nums[deque[deque.length-1]] <= nums[i]) deque.pop();
    deque.push(i);
    if (i >= k-1) result.push(nums[deque[head]]);
  }
  return result;
}
Python solution
def solve(input):
    from collections import deque
    nums, k = input['nums'], input['k']
    if not nums or k > len(nums):
        return []
    candidates, result = deque(), []
    for i, value in enumerate(nums):
        while candidates and candidates[0] <= i - k:
            candidates.popleft()
        while candidates and nums[candidates[-1]] <= value:
            candidates.pop()
        candidates.append(i)
        if i >= k - 1:
            result.append(nums[candidates[0]])
    return result
Load this exercise in the editor
Normalize a numeric featureEasy · JavaScript / Python

Input is {values: finite number[]}. Map each value x to (x-min)/(max-min), in original order, rounded to six decimal places. Empty input returns []; a constant column returns all zeros. This is a fit-and-transform exercise on one provided column, not a recommendation to fit preprocessing on a held-out test set. Inputs avoid exact halfway rounding ties.

Handle constant columns before applying min-max scaling.

Public test cases

Varied feature

Input

{
  "values": [
    10,
    15,
    20
  ]
}

Expected output

[
  0,
  0.5,
  1
]
Empty

Input

{
  "values": []
}

Expected output

[]
Constant duplicates

Input

{
  "values": [
    7,
    7,
    7
  ]
}

Expected output

[
  0,
  0,
  0
]
Negative values

Input

{
  "values": [
    -4,
    -1,
    2
  ]
}

Expected output

[
  0,
  0.5,
  1
]
Rounding

Input

{
  "values": [
    0,
    1,
    3
  ]
}

Expected output

[
  0,
  0.333333,
  1
]

Approach

Fit the minimum and maximum once, then apply the same range to every row. Explicitly resolve the zero-range case instead of dividing by zero. In a predictive pipeline, training data supplies these parameters and validation data uses the saved values; values outside the training range can legitimately map below zero or above one. Rounding here is an output contract, not a numerically necessary preprocessing step.

Complexity: O(n) time and O(n) output space, with O(1) working space beyond the output.

JavaScript solution
function solve({values}) {
  if (!values.length) return [];
  let low=values[0], high=values[0];
  for (const value of values) {low=Math.min(low,value); high=Math.max(high,value);}
  return high===low ? values.map(()=>0) : values.map(value=>Number(((value-low)/(high-low)).toFixed(6)));
}
Python solution
def solve(input):
    values = input['values']
    if not values:
        return []
    low, high = min(values), max(values)
    return [0 for _ in values] if high == low else [round((value - low) / (high - low), 6) for value in values]
Load this exercise in the editor
Score a thresholded classifierMedium · JavaScript / Python

Input is {labels: [0 or 1], scores: finite numbers[], threshold: number}, with equal-length labels and scores. Predict positive exactly when score >= threshold. Return {tp,tn,fp,fn,precision,recall,f1}; round the three ratios to six places. Precision is tp/(tp+fp), recall is tp/(tp+fn), and F1 is 2tp/(2tp+fp+fn). A zero denominator yields 0. Empty inputs yield zero counts and ratios. Inputs avoid halfway rounding ties.

Make threshold and zero-denominator conventions explicit.

Public test cases

Mixed outcomes

Input

{
  "labels": [
    1,
    0,
    1,
    0
  ],
  "scores": [
    0.9,
    0.8,
    0.2,
    0.1
  ],
  "threshold": 0.5
}

Expected output

{
  "tp": 1,
  "tn": 1,
  "fp": 1,
  "fn": 1,
  "precision": 0.5,
  "recall": 0.5,
  "f1": 0.5
}
Empty

Input

{
  "labels": [],
  "scores": [],
  "threshold": 0.5
}

Expected output

{
  "tp": 0,
  "tn": 0,
  "fp": 0,
  "fn": 0,
  "precision": 0,
  "recall": 0,
  "f1": 0
}
Inclusive threshold

Input

{
  "labels": [
    1,
    0
  ],
  "scores": [
    0.5,
    0.49
  ],
  "threshold": 0.5
}

Expected output

{
  "tp": 1,
  "tn": 1,
  "fp": 0,
  "fn": 0,
  "precision": 1,
  "recall": 1,
  "f1": 1
}
No predicted positives

Input

{
  "labels": [
    1,
    0
  ],
  "scores": [
    0,
    0
  ],
  "threshold": 1
}

Expected output

{
  "tp": 0,
  "tn": 1,
  "fp": 0,
  "fn": 1,
  "precision": 0,
  "recall": 0,
  "f1": 0
}
All false positives

Input

{
  "labels": [
    0,
    0
  ],
  "scores": [
    1,
    1
  ],
  "threshold": 0.5
}

Expected output

{
  "tp": 0,
  "tn": 0,
  "fp": 2,
  "fn": 0,
  "precision": 0,
  "recall": 0,
  "f1": 0
}

Approach

Count the four outcomes before computing ratios. The direct count-based F1 formula avoids compounding rounding from precision and recall. The chosen zero-denominator convention makes this exercise deterministic; real reports should also expose support counts and define undefined cases. Changing a threshold trades false positives against false negatives and does not improve the underlying ranking by itself.

Complexity: O(n) time and O(1) additional space.

JavaScript solution
function solve({labels,scores,threshold}) {
  let tp=0,tn=0,fp=0,fn=0;
  labels.forEach((label,i)=>{const positive=scores[i]>=threshold; if(label===1){if(positive)tp++;else fn++;}else{if(positive)fp++;else tn++;}});
  const ratio=(n,d)=>d ? Number((n/d).toFixed(6)) : 0;
  return {tp,tn,fp,fn,precision:ratio(tp,tp+fp),recall:ratio(tp,tp+fn),f1:ratio(2*tp,2*tp+fp+fn)};
}
Python solution
def solve(input):
    tp = tn = fp = fn = 0
    for label, score in zip(input['labels'], input['scores']):
        positive = score >= input['threshold']
        if label == 1:
            if positive: tp += 1
            else: fn += 1
        else:
            if positive: fp += 1
            else: tn += 1
    def ratio(n, d):
        return round(n / d, 6) if d else 0
    return dict(tp=tp, tn=tn, fp=fp, fn=fn, precision=ratio(tp, tp+fp), recall=ratio(tp, tp+fn), f1=ratio(2*tp, 2*tp+fp+fn))
Load this exercise in the editor
Evaluate retrieval with duplicate slotsMedium · JavaScript / Python

Input is {ranked: string[], relevant: string[], k: nonnegative integer}. Inspect the first k ranked positions; repeated IDs still consume positions but can earn at most one hit. Return {hits,precision,recall}, where hits is the unique relevant IDs in first-hit order. Precision uses hits.length/k even when fewer than k results were returned; k=0 gives 0. Recall uses the number of distinct relevant IDs; an empty relevant set gives 0. Round ratios to six places. This denominator convention is explicit for this exercise.

Separate ranked positions from unique relevant documents.

Public test cases

Relevant and irrelevant

Input

{
  "ranked": [
    "a",
    "x",
    "b"
  ],
  "relevant": [
    "a",
    "b",
    "c"
  ],
  "k": 3
}

Expected output

{
  "hits": [
    "a",
    "b"
  ],
  "precision": 0.666667,
  "recall": 0.666667
}
Empty truth

Input

{
  "ranked": [],
  "relevant": [],
  "k": 3
}

Expected output

{
  "hits": [],
  "precision": 0,
  "recall": 0
}
Duplicate consumes slot

Input

{
  "ranked": [
    "a",
    "a",
    "b"
  ],
  "relevant": [
    "a",
    "b",
    "b"
  ],
  "k": 2
}

Expected output

{
  "hits": [
    "a"
  ],
  "precision": 0.5,
  "recall": 0.5
}
Zero cutoff

Input

{
  "ranked": [
    "a"
  ],
  "relevant": [
    "a"
  ],
  "k": 0
}

Expected output

{
  "hits": [],
  "precision": 0,
  "recall": 0
}
Short result list

Input

{
  "ranked": [
    "a"
  ],
  "relevant": [
    "a"
  ],
  "k": 4
}

Expected output

{
  "hits": [
    "a"
  ],
  "precision": 0.25,
  "recall": 1
}

Approach

A relevant set deduplicates the denominator, while a separate seen set prevents rewarding the same document twice. Deduplicating ranked results before applying the cutoff would move later documents into earlier slots and score a different system. Metric definitions vary, so an evaluation contract must state both the cutoff behavior and how missing result slots are treated.

Complexity: O(R + min(k,N)) expected time and O(R + min(k,N)) space, where R is the relevant list length and N the ranked length.

JavaScript solution
function solve({ranked,relevant,k}) {
  const truth=new Set(relevant),seen=new Set(),hits=[];
  for (const id of ranked.slice(0,k)) if(truth.has(id)&&!seen.has(id)){seen.add(id);hits.push(id);}
  const ratio=(n,d)=>d?Number((n/d).toFixed(6)):0;
  return {hits,precision:ratio(hits.length,k),recall:ratio(hits.length,truth.size)};
}
Python solution
def solve(input):
    truth, seen, hits = set(input['relevant']), set(), []
    for doc in input['ranked'][:input['k']]:
        if doc in truth and doc not in seen:
            seen.add(doc)
            hits.append(doc)
    def ratio(n, d):
        return round(n / d, 6) if d else 0
    return dict(hits=hits, precision=ratio(len(hits), input['k']), recall=ratio(len(hits), len(truth)))
Load this exercise in the editor
Compute a vector similarity safelyEasy · JavaScript / Python

Input is {a: finite number[], b: finite number[]} of equal length. Return dot(a,b)/(norm(a)*norm(b)), rounded to six decimal places. Return 0 if either vector has zero norm, including empty vectors. Inputs have moderate magnitudes, so intermediate sums do not overflow. Do not treat negative similarity as an error. Inputs avoid exact halfway rounding ties.

Distinguish zero-vector handling from floating-point rounding.

Public test cases

Partial alignment

Input

{
  "a": [
    1,
    1
  ],
  "b": [
    1,
    0
  ]
}

Expected output

0.707107
Empty

Input

{
  "a": [],
  "b": []
}

Expected output

0
Zero vector

Input

{
  "a": [
    0,
    0
  ],
  "b": [
    3,
    4
  ]
}

Expected output

0
Opposite vectors

Input

{
  "a": [
    1,
    2
  ],
  "b": [
    -1,
    -2
  ]
}

Expected output

-1
Orthogonal

Input

{
  "a": [
    1,
    0
  ],
  "b": [
    0,
    1
  ]
}

Expected output

0

Approach

Accumulate a dot product and the two squared norms in one pass. A zero vector has mathematically undefined cosine similarity; returning zero is this exercise's chosen application convention. Normalization removes magnitude, not semantic ambiguity: a high cosine score does not itself establish relevance, truth, or calibrated probability.

Complexity: O(d) time and O(1) additional space for dimension d.

JavaScript solution
function solve({a,b}) {
  let dot=0,aa=0,bb=0;
  for(let i=0;i<a.length;i++){dot+=a[i]*b[i];aa+=a[i]*a[i];bb+=b[i]*b[i];}
  return aa===0||bb===0 ? 0 : Number((dot/Math.sqrt(aa*bb)).toFixed(6));
}
Python solution
def solve(input):
    import math
    a, b = input['a'], input['b']
    dot = sum(x*y for x,y in zip(a,b))
    aa, bb = sum(x*x for x in a), sum(y*y for y in b)
    return round(dot / math.sqrt(aa*bb), 6) if aa and bb else 0
Load this exercise in the editor
Aggregate a feature by groupEasy · JavaScript / Python

Input is {rows: [{group: ASCII string, value: finite number}, ...]}. Return [{group,count,mean}, ...] sorted by standard ASCII group order. Every row contributes, including duplicate rows. Round means to six places. Empty input returns []. Group names are arbitrary strings, including names such as __proto__. Inputs avoid halfway rounding ties.

Preserve counts alongside means and deterministic ordering.

Public test cases

Mixed groups

Input

{
  "rows": [
    {
      "group": "b",
      "value": 10
    },
    {
      "group": "a",
      "value": 2
    },
    {
      "group": "a",
      "value": 4
    }
  ]
}

Expected output

[
  {
    "group": "a",
    "count": 2,
    "mean": 3
  },
  {
    "group": "b",
    "count": 1,
    "mean": 10
  }
]
Empty

Input

{
  "rows": []
}

Expected output

[]
Duplicate observations

Input

{
  "rows": [
    {
      "group": "x",
      "value": 2
    },
    {
      "group": "x",
      "value": 2
    }
  ]
}

Expected output

[
  {
    "group": "x",
    "count": 2,
    "mean": 2
  }
]
Negative average

Input

{
  "rows": [
    {
      "group": "x",
      "value": -5
    },
    {
      "group": "x",
      "value": 2
    }
  ]
}

Expected output

[
  {
    "group": "x",
    "count": 2,
    "mean": -1.5
  }
]
Arbitrary string key

Input

{
  "rows": [
    {
      "group": "__proto__",
      "value": 7
    }
  ]
}

Expected output

[
  {
    "group": "__proto__",
    "count": 1,
    "mean": 7
  }
]

Approach

Store count and sum for each group, then compute a weighted mean from those totals. Do not average intermediate group means without their counts. JavaScript Map accepts arbitrary string keys without object-prototype collisions. Sorting belongs at the output boundary; it makes tests and reports reproducible without changing the aggregate.

Complexity: O(n + g log g) expected time and O(g) additional space for n rows and g groups.

JavaScript solution
function solve({rows}) {
  const groups=new Map();
  for(const {group,value} of rows){const state=groups.get(group)||{count:0,sum:0};state.count++;state.sum+=value;groups.set(group,state);}
  return [...groups.keys()].sort().map(group=>{const {count,sum}=groups.get(group);return {group,count,mean:Number((sum/count).toFixed(6))};});
}
Python solution
def solve(input):
    groups = {}
    for row in input['rows']:
        count, total = groups.get(row['group'], (0, 0))
        groups[row['group']] = (count+1, total+row['value'])
    return [dict(group=group, count=groups[group][0], mean=round(groups[group][1]/groups[group][0], 6)) for group in sorted(groups)]
Load this exercise in the editor
Keep the latest entity recordMedium · JavaScript / Python

Input is {rows: [{id: ASCII string, at: YYYY-MM-DDTHH:mm:ss.sssZ, value: JSON value}, ...]}. All timestamps are valid fixed-width UTC ISO strings. Keep the latest at for each id; an exact timestamp tie chooses the later input row. Return the complete selected records sorted by ASCII id. Empty input returns []. Lexicographic timestamp comparison is valid only because this input format is fixed and normalized.

Define event-time and equal-time replacement behavior.

Public test cases

Out of order

Input

{
  "rows": [
    {
      "id": "b",
      "at": "2026-01-03T00:00:00.000Z",
      "value": 3
    },
    {
      "id": "a",
      "at": "2026-01-02T00:00:00.000Z",
      "value": 2
    },
    {
      "id": "a",
      "at": "2026-01-01T00:00:00.000Z",
      "value": 1
    }
  ]
}

Expected output

[
  {
    "id": "a",
    "at": "2026-01-02T00:00:00.000Z",
    "value": 2
  },
  {
    "id": "b",
    "at": "2026-01-03T00:00:00.000Z",
    "value": 3
  }
]
Empty

Input

{
  "rows": []
}

Expected output

[]
Equal-time later input wins

Input

{
  "rows": [
    {
      "id": "x",
      "at": "2026-01-01T00:00:00.000Z",
      "value": "old"
    },
    {
      "id": "x",
      "at": "2026-01-01T00:00:00.000Z",
      "value": "new"
    }
  ]
}

Expected output

[
  {
    "id": "x",
    "at": "2026-01-01T00:00:00.000Z",
    "value": "new"
  }
]
Year boundary

Input

{
  "rows": [
    {
      "id": "x",
      "at": "2025-12-31T23:59:59.999Z",
      "value": 0
    },
    {
      "id": "x",
      "at": "2026-01-01T00:00:00.000Z",
      "value": 1
    }
  ]
}

Expected output

[
  {
    "id": "x",
    "at": "2026-01-01T00:00:00.000Z",
    "value": 1
  }
]

Approach

Compare the candidate event time with the stored event time, not with ingestion order. Using >= deliberately replaces equal-time records with later input. Production event streams often require a stronger version or sequence tie-break, since input order can change during replay. Mixed timezone offsets and variable precision break the simple string comparison assumption.

Complexity: O(n + g log g) expected time and O(g) additional space for g entity IDs.

JavaScript solution
function solve({rows}) {
  const latest=new Map();
  for(const row of rows) if(!latest.has(row.id)||row.at>=latest.get(row.id).at) latest.set(row.id,row);
  return [...latest.keys()].sort().map(id=>latest.get(id));
}
Python solution
def solve(input):
    latest = {}
    for row in input['rows']:
        if row['id'] not in latest or row['at'] >= latest[row['id']]['at']:
            latest[row['id']] = row
    return [latest[key] for key in sorted(latest)]
Load this exercise in the editor
Build an ordered confusion matrixMedium · JavaScript / Python

Input is {labels: distinct string[], pairs: [{actual,predicted}, ...]}. Return {matrix,ignored}. Matrix row i is actual labels[i], column j is predicted labels[j]. A pair with either label absent from labels is ignored and increments ignored once. Duplicate observations count separately. Empty labels produce [] and every pair is ignored.

Keep class order and unknown-label handling explicit.

Public test cases

Off-diagonal errors

Input

{
  "labels": [
    "cat",
    "dog"
  ],
  "pairs": [
    {
      "actual": "cat",
      "predicted": "cat"
    },
    {
      "actual": "cat",
      "predicted": "dog"
    },
    {
      "actual": "dog",
      "predicted": "cat"
    }
  ]
}

Expected output

{
  "matrix": [
    [
      1,
      1
    ],
    [
      1,
      0
    ]
  ],
  "ignored": 0
}
No labels

Input

{
  "labels": [],
  "pairs": [
    {
      "actual": "x",
      "predicted": "x"
    }
  ]
}

Expected output

{
  "matrix": [],
  "ignored": 1
}
Duplicate observations

Input

{
  "labels": [
    "x"
  ],
  "pairs": [
    {
      "actual": "x",
      "predicted": "x"
    },
    {
      "actual": "x",
      "predicted": "x"
    }
  ]
}

Expected output

{
  "matrix": [
    [
      2
    ]
  ],
  "ignored": 0
}
Unknown pair counted once

Input

{
  "labels": [
    "x"
  ],
  "pairs": [
    {
      "actual": "other",
      "predicted": "unknown"
    },
    {
      "actual": "x",
      "predicted": "other"
    }
  ]
}

Expected output

{
  "matrix": [
    [
      0
    ]
  ],
  "ignored": 2
}
No observations

Input

{
  "labels": [
    "b",
    "a"
  ],
  "pairs": []
}

Expected output

{
  "matrix": [
    [
      0,
      0
    ],
    [
      0,
      0
    ]
  ],
  "ignored": 0
}

Approach

Build the label-to-index map from the supplied order and allocate independent matrix rows. In Python, multiplying a single inner list would alias rows and corrupt several counts at once. In reports, publish the row/column convention and ignored count: a clean-looking matrix can hide unsupported labels if omissions are invisible.

Complexity: O(c^2 + n) expected time and O(c^2) additional space for c classes and n observations.

JavaScript solution
function solve({labels,pairs}) {
  const index=new Map(labels.map((label,i)=>[label,i]));
  const matrix=labels.map(()=>labels.map(()=>0));let ignored=0;
  for(const pair of pairs){if(!index.has(pair.actual)||!index.has(pair.predicted)){ignored++;continue;}matrix[index.get(pair.actual)][index.get(pair.predicted)]++;}
  return {matrix,ignored};
}
Python solution
def solve(input):
    index = {label: i for i, label in enumerate(input['labels'])}
    matrix = [[0 for _ in index] for _ in index]
    ignored = 0
    for pair in input['pairs']:
        if pair['actual'] not in index or pair['predicted'] not in index:
            ignored += 1
        else:
            matrix[index[pair['actual']]][index[pair['predicted']]] += 1
    return dict(matrix=matrix, ignored=ignored)
Load this exercise in the editor
Split rows without splitting groupsMedium · JavaScript / Python

Input is {rows: [{id: string, group: ASCII string}, ...], testPercent: integer from 0 through 100}. Compute each group bucket as the sum of its ASCII character codes modulo 100. A row goes to test exactly when its bucket < testPercent; otherwise train. Return {train: [row IDs], test: [row IDs]} preserving input order within each split. Duplicate row IDs remain duplicated. This deliberately simple partition is deterministic, not random, cryptographic, or guaranteed representative.

Implement a transparent group-level partition rule.

Public test cases

Keep group together

Input

{
  "rows": [
    {
      "id": "1",
      "group": "a"
    },
    {
      "id": "2",
      "group": "b"
    },
    {
      "id": "3",
      "group": "a"
    }
  ],
  "testPercent": 98
}

Expected output

{
  "train": [
    "2"
  ],
  "test": [
    "1",
    "3"
  ]
}
Empty

Input

{
  "rows": [],
  "testPercent": 50
}

Expected output

{
  "train": [],
  "test": []
}
Zero percent

Input

{
  "rows": [
    {
      "id": "x",
      "group": ""
    }
  ],
  "testPercent": 0
}

Expected output

{
  "train": [
    "x"
  ],
  "test": []
}
Hundred percent duplicates

Input

{
  "rows": [
    {
      "id": "x",
      "group": "b"
    },
    {
      "id": "x",
      "group": "b"
    }
  ],
  "testPercent": 100
}

Expected output

{
  "train": [],
  "test": [
    "x",
    "x"
  ]
}
Strict boundary

Input

{
  "rows": [
    {
      "id": "a",
      "group": "a"
    },
    {
      "id": "A",
      "group": "A"
    }
  ],
  "testPercent": 97
}

Expected output

{
  "train": [
    "a"
  ],
  "test": [
    "A"
  ]
}

Approach

Cache a bucket per group so all related rows share a partition. Group separation prevents one common entity-leakage path, but does not address temporal leakage or duplicate entities represented under different keys. The toy hash can collide and correlate with group naming; a real split should use a documented stronger hash and audit distributions rather than interpreting the requested percentage as an exact row proportion.

Complexity: O(n + L) time and O(g + n) space including outputs, where L is the total character count of distinct group names and g their count.

JavaScript solution
function solve({rows,testPercent}) {
  const result={train:[],test:[]},buckets=new Map();
  for(const row of rows){if(!buckets.has(row.group)){let sum=0;for(const ch of row.group)sum=(sum+ch.charCodeAt(0))%100;buckets.set(row.group,sum);}result[buckets.get(row.group)<testPercent?'test':'train'].push(row.id);}
  return result;
}
Python solution
def solve(input):
    result, buckets = dict(train=[], test=[]), {}
    for row in input['rows']:
        if row['group'] not in buckets:
            buckets[row['group']] = sum(map(ord, row['group'])) % 100
        split = 'test' if buckets[row['group']] < input['testPercent'] else 'train'
        result[split].append(row['id'])
    return result
Load this exercise in the editor
Include customers with no ordersEasy · SQL

Schema: customers(id INTEGER PRIMARY KEY, name TEXT); orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount_cents INTEGER). Return customer_id, order_count, total_cents for every customer, ordered by customer_id. Include customers without orders with both counts and totals 0. Orders referencing absent customers are excluded. Duplicate amounts are distinct orders. Amounts are signed integer cents, never floating currency. The final SELECT result is graded as SQLite column names and rows.

Use a left join without turning its empty side into a fake order.

Public test cases

Customers and duplicate amounts

Input

{
  "setup": [
    "CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount_cents INTEGER NOT NULL);",
    "INSERT INTO customers VALUES (1,'Ada'),(2,'Bo');",
    "INSERT INTO orders VALUES (1,1,500),(2,1,500);"
  ]
}

Expected output

[
  {
    "columns": [
      "customer_id",
      "order_count",
      "total_cents"
    ],
    "values": [
      [
        1,
        2,
        1000
      ],
      [
        2,
        0,
        0
      ]
    ]
  }
]
Empty customers

Input

{
  "setup": [
    "CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount_cents INTEGER NOT NULL);"
  ]
}

Expected output

[]
No orders

Input

{
  "setup": [
    "CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount_cents INTEGER NOT NULL);",
    "INSERT INTO customers VALUES (2,'Bo'),(1,'Ada');"
  ]
}

Expected output

[
  {
    "columns": [
      "customer_id",
      "order_count",
      "total_cents"
    ],
    "values": [
      [
        1,
        0,
        0
      ],
      [
        2,
        0,
        0
      ]
    ]
  }
]
Signed cents and orphan

Input

{
  "setup": [
    "CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount_cents INTEGER NOT NULL);",
    "INSERT INTO customers VALUES (1,'Ada');",
    "INSERT INTO orders VALUES (1,1,700),(2,1,-200),(3,99,900);"
  ]
}

Expected output

[
  {
    "columns": [
      "customer_id",
      "order_count",
      "total_cents"
    ],
    "values": [
      [
        1,
        2,
        500
      ]
    ]
  }
]

Approach

Start with customers and preserve them through LEFT JOIN. COUNT(o.id) ignores the null extended row; COUNT(*) would report one order for a customer with none. SUM over no matching orders is null, so COALESCE supplies the required zero. A WHERE condition on the order side could accidentally discard customers without matches; place matching conditions deliberately.

Complexity: Planner-dependent. Without an index on orders.customer_id, scans and join work can dominate; an index can help the join. Aggregation requires state proportional to customer groups, and ORDER BY may need a sort.

SQL solution
SELECT c.id AS customer_id,
       COUNT(o.id) AS order_count,
       COALESCE(SUM(o.amount_cents), 0) AS total_cents
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id
ORDER BY c.id;
Load this exercise in the editor
Select each user's latest eventMedium · SQL

Schema: events(id INTEGER PRIMARY KEY, user_id TEXT, at TEXT, type TEXT). Timestamps are normalized fixed-width UTC ISO strings. Return user_id, event_id, type for the latest event per user. Break equal timestamp ties by greatest id. Order output by user_id using SQLite's default binary collation. An event ID is unique globally. Do not combine MAX(at) with an arbitrary ungrouped type.

Use a window function and a complete ordering key.

Public test cases

Out-of-order events

Input

{
  "setup": [
    "CREATE TABLE events(id INTEGER PRIMARY KEY, user_id TEXT NOT NULL, at TEXT NOT NULL, type TEXT NOT NULL);",
    "INSERT INTO events VALUES (1,'b','2026-01-01T00:00:00.000Z','open'),(2,'a','2026-01-02T00:00:00.000Z','save'),(3,'a','2026-01-01T00:00:00.000Z','open');"
  ]
}

Expected output

[
  {
    "columns": [
      "user_id",
      "event_id",
      "type"
    ],
    "values": [
      [
        "a",
        2,
        "save"
      ],
      [
        "b",
        1,
        "open"
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE events(id INTEGER PRIMARY KEY, user_id TEXT NOT NULL, at TEXT NOT NULL, type TEXT NOT NULL);"
  ]
}

Expected output

[]
Timestamp tie

Input

{
  "setup": [
    "CREATE TABLE events(id INTEGER PRIMARY KEY, user_id TEXT NOT NULL, at TEXT NOT NULL, type TEXT NOT NULL);",
    "INSERT INTO events VALUES (1,'x','2026-01-01T00:00:00.000Z','old'),(2,'x','2026-01-01T00:00:00.000Z','new');"
  ]
}

Expected output

[
  {
    "columns": [
      "user_id",
      "event_id",
      "type"
    ],
    "values": [
      [
        "x",
        2,
        "new"
      ]
    ]
  }
]
Year boundary and repeated type

Input

{
  "setup": [
    "CREATE TABLE events(id INTEGER PRIMARY KEY, user_id TEXT NOT NULL, at TEXT NOT NULL, type TEXT NOT NULL);",
    "INSERT INTO events VALUES (10,'x','2025-12-31T23:59:59.999Z','view'),(9,'x','2026-01-01T00:00:00.000Z','view');"
  ]
}

Expected output

[
  {
    "columns": [
      "user_id",
      "event_id",
      "type"
    ],
    "values": [
      [
        "x",
        9,
        "view"
      ]
    ]
  }
]

Approach

ROW_NUMBER assigns one position per user according to a total order. Filtering position=1 selects an entire event row, preserving the relationship among timestamp, id, and type. MAX(id) alone does not guarantee the latest timestamp. Normalized UTC strings permit this text ordering; inconsistent offsets or timestamp precision require normalization before the query.

Complexity: Typically O(n log n) sorting work for the window without a suitable index, plus output sorting. A composite index on (user_id, at DESC, id DESC) may reduce sorting; inspect EXPLAIN QUERY PLAN rather than promising a universal plan.

SQL solution
WITH ranked AS (
  SELECT user_id, id, type,
         ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY at DESC, id DESC) AS position
  FROM events
)
SELECT user_id, id AS event_id, type
FROM ranked
WHERE position = 1
ORDER BY user_id;
Load this exercise in the editor
Summarize signed paid revenueEasy · SQL

Schema: orders(id INTEGER PRIMARY KEY, day TEXT, status TEXT, amount_cents INTEGER). day is normalized YYYY-MM-DD. Sum amount_cents only for rows whose status is exactly paid. Return day, revenue_cents ordered by day. Negative paid amounts reduce revenue. Include days with paid rows summing to zero; omit days with no paid rows. Each distinct order ID contributes, even if its other fields duplicate another order.

Filter the business event before aggregating its amount.

Public test cases

Filter and aggregate

Input

{
  "setup": [
    "CREATE TABLE orders(id INTEGER PRIMARY KEY, day TEXT NOT NULL, status TEXT NOT NULL, amount_cents INTEGER NOT NULL);",
    "INSERT INTO orders VALUES (1,'2026-01-02','paid',300),(2,'2026-01-01','paid',500),(3,'2026-01-01','pending',900),(4,'2026-01-01','paid',500);"
  ]
}

Expected output

[
  {
    "columns": [
      "day",
      "revenue_cents"
    ],
    "values": [
      [
        "2026-01-01",
        1000
      ],
      [
        "2026-01-02",
        300
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE orders(id INTEGER PRIMARY KEY, day TEXT NOT NULL, status TEXT NOT NULL, amount_cents INTEGER NOT NULL);"
  ]
}

Expected output

[]
Only unpaid

Input

{
  "setup": [
    "CREATE TABLE orders(id INTEGER PRIMARY KEY, day TEXT NOT NULL, status TEXT NOT NULL, amount_cents INTEGER NOT NULL);",
    "INSERT INTO orders VALUES (1,'2026-01-01','failed',500);"
  ]
}

Expected output

[]
Refund and zero day

Input

{
  "setup": [
    "CREATE TABLE orders(id INTEGER PRIMARY KEY, day TEXT NOT NULL, status TEXT NOT NULL, amount_cents INTEGER NOT NULL);",
    "INSERT INTO orders VALUES (1,'2026-01-01','paid',400),(2,'2026-01-01','paid',-400),(3,'2026-01-02','paid',-100);"
  ]
}

Expected output

[
  {
    "columns": [
      "day",
      "revenue_cents"
    ],
    "values": [
      [
        "2026-01-01",
        0
      ],
      [
        "2026-01-02",
        -100
      ]
    ]
  }
]

Approach

WHERE chooses eligible rows before GROUP BY. Do not use SUM(DISTINCT amount_cents): equal amounts can belong to separate legitimate orders. An all-zero day is still an observed paid day, so a HAVING total>0 condition would violate the specification. Integer cents avoid binary decimal representation issues, although very large totals still require range checks in production.

Complexity: A scan is O(n); grouping and ordering are planner-dependent and can require O(n log n) work. Indexes on status and day can help selective workloads but add write cost.

SQL solution
SELECT day, SUM(amount_cents) AS revenue_cents
FROM orders
WHERE status = 'paid'
GROUP BY day
ORDER BY day;
Load this exercise in the editor
Find tickets with no repliesEasy · SQL

Schema: tickets(id INTEGER PRIMARY KEY); replies(id INTEGER PRIMARY KEY, ticket_id INTEGER). Return id for tickets having no reply, ordered ascending. Several replies to the same ticket still mean that ticket is answered. Replies referencing absent tickets do not affect the result. Empty tickets return no rows.

Express absence without multiplying or counting join rows.

Public test cases

Mixed answered tickets

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE replies(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (1),(2),(3);",
    "INSERT INTO replies VALUES (1,2),(2,2);"
  ]
}

Expected output

[
  {
    "columns": [
      "id"
    ],
    "values": [
      [
        1
      ],
      [
        3
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE replies(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);"
  ]
}

Expected output

[]
No replies

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE replies(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (2),(1);"
  ]
}

Expected output

[
  {
    "columns": [
      "id"
    ],
    "values": [
      [
        1
      ],
      [
        2
      ]
    ]
  }
]
All answered and orphan

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE replies(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (1);",
    "INSERT INTO replies VALUES (1,1),(2,99);"
  ]
}

Expected output

[]

Approach

NOT EXISTS asks whether any matching reply exists and avoids producing duplicate ticket rows. This also avoids the null-sensitive behavior of NOT IN when a subquery can contain nulls. The provided schema forbids null reply links, but understanding that difference matters when schemas evolve. An index on replies.ticket_id is a natural support for the correlated existence test.

Complexity: With an index, each existence lookup is typically logarithmic or stops at the first match. Without it, a naive plan can scan replies for each ticket; use EXPLAIN QUERY PLAN for the actual workload.

SQL solution
SELECT t.id
FROM tickets AS t
WHERE NOT EXISTS (SELECT 1 FROM replies AS r WHERE r.ticket_id = t.id)
ORDER BY t.id;
Load this exercise in the editor
Compute account running balancesMedium · SQL

Schema: transactions(id INTEGER PRIMARY KEY, account TEXT, delta_cents INTEGER). Account opening balances are zero. Return account, id, balance_cents after each transaction, ordered by account then id. Within an account, increasing id defines posting order. Negative deltas are valid. Keep every transaction, including zero changes and repeated amounts.

Partition a window and specify its frame explicitly.

Public test cases

Unsorted inserts

Input

{
  "setup": [
    "CREATE TABLE transactions(id INTEGER PRIMARY KEY, account TEXT NOT NULL, delta_cents INTEGER NOT NULL);",
    "INSERT INTO transactions VALUES (3,'a',-20),(1,'a',100),(2,'a',50);"
  ]
}

Expected output

[
  {
    "columns": [
      "account",
      "id",
      "balance_cents"
    ],
    "values": [
      [
        "a",
        1,
        100
      ],
      [
        "a",
        2,
        150
      ],
      [
        "a",
        3,
        130
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE transactions(id INTEGER PRIMARY KEY, account TEXT NOT NULL, delta_cents INTEGER NOT NULL);"
  ]
}

Expected output

[]
Separate accounts

Input

{
  "setup": [
    "CREATE TABLE transactions(id INTEGER PRIMARY KEY, account TEXT NOT NULL, delta_cents INTEGER NOT NULL);",
    "INSERT INTO transactions VALUES (1,'b',9),(2,'a',5),(3,'b',-10);"
  ]
}

Expected output

[
  {
    "columns": [
      "account",
      "id",
      "balance_cents"
    ],
    "values": [
      [
        "a",
        2,
        5
      ],
      [
        "b",
        1,
        9
      ],
      [
        "b",
        3,
        -1
      ]
    ]
  }
]
Repeated and zero amounts

Input

{
  "setup": [
    "CREATE TABLE transactions(id INTEGER PRIMARY KEY, account TEXT NOT NULL, delta_cents INTEGER NOT NULL);",
    "INSERT INTO transactions VALUES (1,'x',2),(2,'x',2),(3,'x',0);"
  ]
}

Expected output

[
  {
    "columns": [
      "account",
      "id",
      "balance_cents"
    ],
    "values": [
      [
        "x",
        1,
        2
      ],
      [
        "x",
        2,
        4
      ],
      [
        "x",
        3,
        4
      ]
    ]
  }
]

Approach

PARTITION BY resets the accumulator for each account. ORDER BY inside the window controls accumulation; the final ORDER BY controls displayed row order. The explicit ROWS frame states that each row adds one transaction. A plain GROUP BY would collapse the account to one total and lose the transaction-by-transaction result.

Complexity: Generally O(n log n) when sorting by account and id is required, with a linear window pass afterward. An (account,id) index may support the ordering. The query returns O(n) rows.

SQL solution
SELECT account, id,
       SUM(delta_cents) OVER (
         PARTITION BY account ORDER BY id
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS balance_cents
FROM transactions
ORDER BY account, id;
Load this exercise in the editor
Keep the top two salary bandsMedium · SQL

Schema: employees(id INTEGER PRIMARY KEY, team TEXT, name TEXT, salary_cents INTEGER). Return employees in the top two distinct salary levels within each team, including every tie. Columns: team, name, salary_cents, salary_rank. Rank 1 is the highest salary. Order by team, salary_rank, name, then id. A team with one level returns all its employees. This is two salary bands, not two people.

Choose dense ranking when tied levels should count once.

Public test cases

Tied top level

Input

{
  "setup": [
    "CREATE TABLE employees(id INTEGER PRIMARY KEY, team TEXT NOT NULL, name TEXT NOT NULL, salary_cents INTEGER NOT NULL);",
    "INSERT INTO employees VALUES (1,'a','Ada',100),(2,'a','Bo',100),(3,'a','Cy',90),(4,'a','Di',80);"
  ]
}

Expected output

[
  {
    "columns": [
      "team",
      "name",
      "salary_cents",
      "salary_rank"
    ],
    "values": [
      [
        "a",
        "Ada",
        100,
        1
      ],
      [
        "a",
        "Bo",
        100,
        1
      ],
      [
        "a",
        "Cy",
        90,
        2
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE employees(id INTEGER PRIMARY KEY, team TEXT NOT NULL, name TEXT NOT NULL, salary_cents INTEGER NOT NULL);"
  ]
}

Expected output

[]
Only one level

Input

{
  "setup": [
    "CREATE TABLE employees(id INTEGER PRIMARY KEY, team TEXT NOT NULL, name TEXT NOT NULL, salary_cents INTEGER NOT NULL);",
    "INSERT INTO employees VALUES (2,'x','Bo',50),(1,'x','Ada',50);"
  ]
}

Expected output

[
  {
    "columns": [
      "team",
      "name",
      "salary_cents",
      "salary_rank"
    ],
    "values": [
      [
        "x",
        "Ada",
        50,
        1
      ],
      [
        "x",
        "Bo",
        50,
        1
      ]
    ]
  }
]
Independent team ranks

Input

{
  "setup": [
    "CREATE TABLE employees(id INTEGER PRIMARY KEY, team TEXT NOT NULL, name TEXT NOT NULL, salary_cents INTEGER NOT NULL);",
    "INSERT INTO employees VALUES (1,'a','Ada',10),(2,'b','Bo',100),(3,'b','Cy',90),(4,'b','Di',80);"
  ]
}

Expected output

[
  {
    "columns": [
      "team",
      "name",
      "salary_cents",
      "salary_rank"
    ],
    "values": [
      [
        "a",
        "Ada",
        10,
        1
      ],
      [
        "b",
        "Bo",
        100,
        1
      ],
      [
        "b",
        "Cy",
        90,
        2
      ]
    ]
  }
]

Approach

DENSE_RANK gives equal salaries the same rank without gaps. ROW_NUMBER would arbitrarily truncate tied people, while RANK could assign the next salary level rank 3 after two tied leaders and incorrectly exclude it. id completes the display tie-break without changing the salary ranking itself. Keep those two ordering purposes separate.

Complexity: Typically O(n log n) for partition ordering and final presentation; an appropriate composite index can help. Returning ties means output size is not bounded to two rows per team.

SQL solution
WITH ranked AS (
  SELECT id, team, name, salary_cents,
         DENSE_RANK() OVER (PARTITION BY team ORDER BY salary_cents DESC) AS salary_rank
  FROM employees
)
SELECT team, name, salary_cents, salary_rank
FROM ranked
WHERE salary_rank <= 2
ORDER BY team, salary_rank, name, id;
Load this exercise in the editor
Measure next-calendar-month retentionHard · SQL

Schema: activity(user_id TEXT, day TEXT), with valid normalized YYYY-MM-DD days. A user's cohort is the calendar month of their earliest activity in this table. A user is retained_m1 when they have any activity in the immediately following calendar month. Return cohort (YYYY-MM), users, retained_m1, ordered by cohort. Count each user once; repeated events do not inflate either count. December advances to January of the next year. This observed-activity definition is not an account-signup cohort, and partially observed future months are not maturity-adjusted.

Separate user cohorts from repeated activity events.

Public test cases

Repeated activity counts once

Input

{
  "setup": [
    "CREATE TABLE activity(user_id TEXT NOT NULL, day TEXT NOT NULL);",
    "INSERT INTO activity VALUES ('a','2026-01-01'),('a','2026-02-01'),('a','2026-02-01'),('b','2026-01-03');"
  ]
}

Expected output

[
  {
    "columns": [
      "cohort",
      "users",
      "retained_m1"
    ],
    "values": [
      [
        "2026-01",
        2,
        1
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE activity(user_id TEXT NOT NULL, day TEXT NOT NULL);"
  ]
}

Expected output

[]
December boundary

Input

{
  "setup": [
    "CREATE TABLE activity(user_id TEXT NOT NULL, day TEXT NOT NULL);",
    "INSERT INTO activity VALUES ('a','2025-12-31'),('a','2026-01-01'),('b','2026-01-02'),('b','2026-02-01');"
  ]
}

Expected output

[
  {
    "columns": [
      "cohort",
      "users",
      "retained_m1"
    ],
    "values": [
      [
        "2025-12",
        1,
        1
      ],
      [
        "2026-01",
        1,
        1
      ]
    ]
  }
]
Skipped month is not retained

Input

{
  "setup": [
    "CREATE TABLE activity(user_id TEXT NOT NULL, day TEXT NOT NULL);",
    "INSERT INTO activity VALUES ('a','2026-01-01'),('a','2026-03-01'),('a','2026-01-01');"
  ]
}

Expected output

[
  {
    "columns": [
      "cohort",
      "users",
      "retained_m1"
    ],
    "values": [
      [
        "2026-01",
        1,
        0
      ]
    ]
  }
]

Approach

First form one row per user with its observed cohort, then test existence in the next month. EXISTS does not multiply a user with many events. Construct the first day of the cohort month before adding a month, avoiding end-of-month date arithmetic surprises. An actual dashboard should identify cohorts whose next month is incomplete rather than interpreting their early counts as comparable retention rates.

Complexity: Planner-dependent: grouping scans activity, while a naive correlated EXISTS can be expensive. An index on activity(user_id,day) helps matching users; stored normalized month keys can improve month filtering. Verify plans and maturity rules on realistic data.

SQL solution
WITH cohorts AS (
  SELECT user_id, substr(MIN(day), 1, 7) AS cohort
  FROM activity
  GROUP BY user_id
), user_retention AS (
  SELECT c.user_id, c.cohort,
         CASE WHEN EXISTS (
           SELECT 1 FROM activity AS a
           WHERE a.user_id = c.user_id
             AND substr(a.day, 1, 7) = strftime('%Y-%m', c.cohort || '-01', '+1 month')
         ) THEN 1 ELSE 0 END AS retained
  FROM cohorts AS c
)
SELECT cohort, COUNT(*) AS users, SUM(retained) AS retained_m1
FROM user_retention
GROUP BY cohort
ORDER BY cohort;
Load this exercise in the editor
Avoid multiplying one-to-many joinsMedium · SQL

Schema: tickets(id INTEGER PRIMARY KEY); tags(ticket_id INTEGER, tag TEXT); comments(id INTEGER PRIMARY KEY, ticket_id INTEGER). Return id, tag_count, comment_count for every ticket, ordered by id. tag_count counts distinct tag strings, comment_count counts comment rows. Include zero counts. Orphan child rows are ignored. Joining tags and comments directly would multiply their rows, so make each child aggregate one row per ticket before combining.

Aggregate child tables at the ticket grain before joining.

Public test cases

Two tags and three comments

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE tags(ticket_id INTEGER NOT NULL, tag TEXT NOT NULL); CREATE TABLE comments(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (1),(2);",
    "INSERT INTO tags VALUES (1,'a'),(1,'b');",
    "INSERT INTO comments VALUES (1,1),(2,1),(3,1);"
  ]
}

Expected output

[
  {
    "columns": [
      "id",
      "tag_count",
      "comment_count"
    ],
    "values": [
      [
        1,
        2,
        3
      ],
      [
        2,
        0,
        0
      ]
    ]
  }
]
Empty

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE tags(ticket_id INTEGER NOT NULL, tag TEXT NOT NULL); CREATE TABLE comments(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);"
  ]
}

Expected output

[]
Duplicate tag strings

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE tags(ticket_id INTEGER NOT NULL, tag TEXT NOT NULL); CREATE TABLE comments(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (1);",
    "INSERT INTO tags VALUES (1,'a'),(1,'a');",
    "INSERT INTO comments VALUES (1,1),(2,1);"
  ]
}

Expected output

[
  {
    "columns": [
      "id",
      "tag_count",
      "comment_count"
    ],
    "values": [
      [
        1,
        1,
        2
      ]
    ]
  }
]
Orphan rows and empty children

Input

{
  "setup": [
    "CREATE TABLE tickets(id INTEGER PRIMARY KEY); CREATE TABLE tags(ticket_id INTEGER NOT NULL, tag TEXT NOT NULL); CREATE TABLE comments(id INTEGER PRIMARY KEY, ticket_id INTEGER NOT NULL);",
    "INSERT INTO tickets VALUES (2),(1);",
    "INSERT INTO tags VALUES (99,'orphan');",
    "INSERT INTO comments VALUES (1,99);"
  ]
}

Expected output

[
  {
    "columns": [
      "id",
      "tag_count",
      "comment_count"
    ],
    "values": [
      [
        1,
        0,
        0
      ],
      [
        2,
        0,
        0
      ]
    ]
  }
]

Approach

Two tags joined with three comments produce six intermediate rows. Preaggregation makes the intended grain explicit and avoids both inflated counts and reliance on expensive DISTINCT repairs after a multiplied join. Each left join now adds at most one aggregate row per ticket. COALESCE converts absent groups to zero without dropping parent tickets.

Complexity: Scan and aggregate each child table, then join the smaller per-ticket results. Sorting and distinct-tag tracking can add O(n log n) work; exact memory and plans depend on indexes and cardinality.

SQL solution
WITH tag_counts AS (
  SELECT ticket_id, COUNT(DISTINCT tag) AS tag_count
  FROM tags GROUP BY ticket_id
), comment_counts AS (
  SELECT ticket_id, COUNT(*) AS comment_count
  FROM comments GROUP BY ticket_id
)
SELECT t.id, COALESCE(g.tag_count,0) AS tag_count,
       COALESCE(c.comment_count,0) AS comment_count
FROM tickets AS t
LEFT JOIN tag_counts AS g ON g.ticket_id=t.id
LEFT JOIN comment_counts AS c ON c.ticket_id=t.id
ORDER BY t.id;
Load this exercise in the editor

Keep learning beyond the interview.

Explore the Learning Hub