{"id":"code-01","title":"Parse accounting amounts without float errors","difficulty":"easy","language":"python","tags":["parsing","money","decimal"],"problem_statement":"parse_amount in ledger/amounts.py turns amounts from bank exports into numbers. Finance reports two bugs:\n\n1. Amounts in parentheses, the accounting notation for negatives such as \"(1,234.50)\", crash the import.\n2. Totals drift by fractions of a cent because the function returns floats.\n\nFix parse_amount so that it returns a decimal.Decimal, treats parentheses as a negative sign, accepts a leading \"$\", thousands separators and surrounding spaces, and raises ValueError for anything that is not an amount.","starter_files":[{"path":"ledger/amounts.py","content":"def parse_amount(text: str) -> float:\n    return float(text.replace(\"$\", \"\").replace(\",\", \"\"))\n"}],"solution_files":[{"path":"ledger/amounts.py","content":"from decimal import Decimal, InvalidOperation\n\n\ndef parse_amount(text: str) -> Decimal:\n    cleaned = text.strip().replace(\"$\", \"\").replace(\",\", \"\").replace(\" \", \"\")\n    negative = cleaned.startswith(\"(\") and cleaned.endswith(\")\")\n    if negative:\n        cleaned = cleaned[1:-1]\n    try:\n        value = Decimal(cleaned)\n    except InvalidOperation:\n        raise ValueError(f\"not an amount: {text!r}\") from None\n    return -value if negative else value\n"}],"test_files":[{"path":"tests/test_amounts.py","content":"from decimal import Decimal\n\nimport pytest\n\nfrom ledger.amounts import parse_amount\n\n\ndef test_thousands_separator():\n    assert parse_amount(\"1,234.50\") == Decimal(\"1234.50\")\n\n\ndef test_dollar_sign_and_spaces():\n    assert parse_amount(\" $7.00 \") == Decimal(\"7.00\")\n\n\ndef test_returns_decimal():\n    assert isinstance(parse_amount(\"1.10\"), Decimal)\n\n\ndef test_no_float_drift():\n    assert parse_amount(\"0.10\") + parse_amount(\"0.20\") == Decimal(\"0.30\")\n\n\ndef test_parentheses_are_negative():\n    assert parse_amount(\"(1,234.50)\") == Decimal(\"-1234.50\")\n\n\ndef test_rejects_text():\n    with pytest.raises(ValueError):\n        parse_amount(\"abc\")\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_amounts.py::test_returns_decimal","tests/test_amounts.py::test_no_float_drift","tests/test_amounts.py::test_parentheses_are_negative"],"pass_to_pass":["tests/test_amounts.py::test_thousands_separator","tests/test_amounts.py::test_dollar_sign_and_spaces","tests/test_amounts.py::test_rejects_text"],"checked_at":"2026-09-29T23:30:14.485Z"}}
{"id":"code-02","title":"Make the LRU cache evict the least recently used entry","difficulty":"medium","language":"python","tags":["data structures","caching"],"problem_statement":"cache/lru.py has an LRUCache that is supposed to evict the least recently used key when it is full. In production it evicts keys that were read a moment ago: it evicts in insertion order, ignoring reads and updates.\n\nFix it so that get and put both count as a use, eviction removes the least recently used key, and a capacity below 1 raises ValueError. Keep the public interface: LRUCache(capacity), get(key, default=None), put(key, value) and len().","starter_files":[{"path":"cache/lru.py","content":"class LRUCache:\n    def __init__(self, capacity: int):\n        self.capacity = capacity\n        self._data = {}\n\n    def get(self, key, default=None):\n        return self._data.get(key, default)\n\n    def put(self, key, value):\n        if key not in self._data and len(self._data) >= self.capacity:\n            oldest = next(iter(self._data))\n            del self._data[oldest]\n        self._data[key] = value\n\n    def __len__(self):\n        return len(self._data)\n"}],"solution_files":[{"path":"cache/lru.py","content":"from collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity: int):\n        if capacity < 1:\n            raise ValueError(\"capacity must be at least 1\")\n        self.capacity = capacity\n        self._data: OrderedDict = OrderedDict()\n\n    def get(self, key, default=None):\n        if key not in self._data:\n            return default\n        self._data.move_to_end(key)\n        return self._data[key]\n\n    def put(self, key, value):\n        if key in self._data:\n            self._data.move_to_end(key)\n        self._data[key] = value\n        if len(self._data) > self.capacity:\n            self._data.popitem(last=False)\n\n    def __len__(self):\n        return len(self._data)\n"}],"test_files":[{"path":"tests/test_lru.py","content":"import pytest\n\nfrom cache.lru import LRUCache\n\n\ndef test_get_and_put():\n    cache = LRUCache(2)\n    cache.put(\"a\", 1)\n    assert cache.get(\"a\") == 1\n    assert cache.get(\"missing\", \"default\") == \"default\"\n\n\ndef test_capacity_is_respected():\n    cache = LRUCache(3)\n    for i in range(10):\n        cache.put(i, i)\n    assert len(cache) == 3\n\n\ndef test_read_counts_as_use():\n    cache = LRUCache(2)\n    cache.put(\"a\", 1)\n    cache.put(\"b\", 2)\n    cache.get(\"a\")\n    cache.put(\"c\", 3)\n    assert cache.get(\"a\") == 1\n    assert cache.get(\"b\") is None\n\n\ndef test_update_counts_as_use():\n    cache = LRUCache(2)\n    cache.put(\"a\", 1)\n    cache.put(\"b\", 2)\n    cache.put(\"a\", 10)\n    cache.put(\"c\", 3)\n    assert cache.get(\"a\") == 10\n    assert cache.get(\"b\") is None\n\n\ndef test_capacity_must_be_positive():\n    with pytest.raises(ValueError):\n        LRUCache(0)\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_lru.py::test_read_counts_as_use","tests/test_lru.py::test_update_counts_as_use","tests/test_lru.py::test_capacity_must_be_positive"],"pass_to_pass":["tests/test_lru.py::test_get_and_put","tests/test_lru.py::test_capacity_is_respected"],"checked_at":"2026-09-29T23:30:15.768Z"}}
{"id":"code-03","title":"Retry only transient errors, with capped backoff","difficulty":"medium","language":"python","tags":["reliability","error handling"],"problem_statement":"net/retry.py has a retry helper used around HTTP calls. It has three problems in production:\n\n1. It retries every exception, so bugs such as ValueError are retried and hidden.\n2. After the last attempt it returns None instead of raising, so callers crash later with confusing errors.\n3. The delay doubles without limit, so a long outage sleeps for minutes.\n\nChange retry so that it only retries exceptions listed in retry_on, raises the last error when attempts run out (without sleeping after the final attempt), and never sleeps longer than max_delay. The delay before retry i (starting at 0) is base_delay * 2**i, capped at max_delay. Keep the signature; tests pass a fake sleep.","starter_files":[{"path":"net/retry.py","content":"import time\n\n\ndef retry(func, attempts=3, base_delay=0.5, max_delay=8.0, retry_on=(ConnectionError,), sleep=time.sleep):\n    for i in range(attempts):\n        try:\n            return func()\n        except Exception:\n            sleep(base_delay * 2 ** i)\n    return None\n"}],"solution_files":[{"path":"net/retry.py","content":"import time\n\n\ndef retry(func, attempts=3, base_delay=0.5, max_delay=8.0, retry_on=(ConnectionError,), sleep=time.sleep):\n    for i in range(attempts):\n        try:\n            return func()\n        except retry_on:\n            if i == attempts - 1:\n                raise\n            sleep(min(base_delay * 2 ** i, max_delay))\n"}],"test_files":[{"path":"tests/test_retry.py","content":"import pytest\n\nfrom net.retry import retry\n\n\ndef flaky(failures, error=ConnectionError, value=\"ok\"):\n    calls = {\"n\": 0}\n\n    def func():\n        calls[\"n\"] += 1\n        if calls[\"n\"] <= failures:\n            raise error(\"boom\")\n        return value\n\n    return func, calls\n\n\ndef test_returns_first_success():\n    delays = []\n    func, _ = flaky(0)\n    assert retry(func, sleep=delays.append) == \"ok\"\n    assert delays == []\n\n\ndef test_retries_then_succeeds():\n    delays = []\n    func, calls = flaky(2)\n    assert retry(func, sleep=delays.append) == \"ok\"\n    assert calls[\"n\"] == 3\n    assert delays == [0.5, 1.0]\n\n\ndef test_raises_after_the_last_attempt():\n    delays = []\n    func, calls = flaky(99)\n    with pytest.raises(ConnectionError):\n        retry(func, attempts=3, sleep=delays.append)\n    assert calls[\"n\"] == 3\n    assert delays == [0.5, 1.0]\n\n\ndef test_does_not_retry_other_errors():\n    delays = []\n    func, calls = flaky(1, error=ValueError)\n    with pytest.raises(ValueError):\n        retry(func, sleep=delays.append)\n    assert calls[\"n\"] == 1\n    assert delays == []\n\n\ndef test_delay_is_capped():\n    delays = []\n    func, _ = flaky(99)\n    with pytest.raises(ConnectionError):\n        retry(func, attempts=6, base_delay=1, max_delay=4, sleep=delays.append)\n    assert delays == [1, 2, 4, 4, 4]\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_retry.py::test_raises_after_the_last_attempt","tests/test_retry.py::test_does_not_retry_other_errors","tests/test_retry.py::test_delay_is_capped"],"pass_to_pass":["tests/test_retry.py::test_returns_first_success","tests/test_retry.py::test_retries_then_succeeds"],"checked_at":"2026-09-29T23:30:17.000Z"}}
{"id":"code-04","title":"Fix an SQL injection in the user lookup","difficulty":"medium","language":"python","tags":["security","sql","sqlite"],"problem_statement":"app/users.py builds its query by pasting the email into the SQL text. A security review found that an email such as ' OR '1'='1 returns another user's record, and customers with an apostrophe in their email (o'brien@example.com) cannot log in at all.\n\nFix find_user so that the email is passed as a query parameter. Emails are case-insensitive: ANA@EXAMPLE.COM must find ana@example.com. Return the same dictionary shape, or None when nobody matches.","starter_files":[{"path":"app/users.py","content":"import sqlite3\n\n\ndef find_user(conn: sqlite3.Connection, email: str):\n    row = conn.execute(f\"SELECT id, email, name FROM users WHERE email = '{email}'\").fetchone()\n    return None if row is None else {\"id\": row[0], \"email\": row[1], \"name\": row[2]}\n"}],"solution_files":[{"path":"app/users.py","content":"import sqlite3\n\n\ndef find_user(conn: sqlite3.Connection, email: str):\n    row = conn.execute(\n        \"SELECT id, email, name FROM users WHERE lower(email) = lower(?)\",\n        (email,),\n    ).fetchone()\n    return None if row is None else {\"id\": row[0], \"email\": row[1], \"name\": row[2]}\n"}],"test_files":[{"path":"tests/test_users.py","content":"import sqlite3\n\nimport pytest\n\nfrom app.users import find_user\n\n\n@pytest.fixture\ndef conn():\n    db = sqlite3.connect(\":memory:\")\n    db.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL, name TEXT NOT NULL)\")\n    db.executemany(\n        \"INSERT INTO users (email, name) VALUES (?, ?)\",\n        [(\"ana@example.com\", \"Ana\"), (\"o'brien@example.com\", \"Siobhan O'Brien\")],\n    )\n    return db\n\n\ndef test_finds_a_user(conn):\n    assert find_user(conn, \"ana@example.com\") == {\"id\": 1, \"email\": \"ana@example.com\", \"name\": \"Ana\"}\n\n\ndef test_missing_user_is_none(conn):\n    assert find_user(conn, \"nobody@example.com\") is None\n\n\ndef test_apostrophe_in_email(conn):\n    assert find_user(conn, \"o'brien@example.com\")[\"name\"] == \"Siobhan O'Brien\"\n\n\ndef test_injection_matches_nobody(conn):\n    assert find_user(conn, \"' OR '1'='1\") is None\n\n\ndef test_email_is_case_insensitive(conn):\n    assert find_user(conn, \"ANA@EXAMPLE.COM\")[\"name\"] == \"Ana\"\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_users.py::test_apostrophe_in_email","tests/test_users.py::test_injection_matches_nobody","tests/test_users.py::test_email_is_case_insensitive"],"pass_to_pass":["tests/test_users.py::test_finds_a_user","tests/test_users.py::test_missing_user_is_none"],"checked_at":"2026-09-29T23:30:18.203Z"}}
{"id":"code-05","title":"Merge calendar intervals correctly","difficulty":"easy","language":"python","tags":["algorithms","intervals"],"problem_statement":"scheduling/merge.py merges busy intervals before we show free slots. Users see a meeting from 10 to 11 and one from 11 to 12 as two blocks with a zero-length gap, and busy times disappear when the calendar API returns intervals out of order.\n\nFix merge so that it accepts intervals in any order, merges intervals that overlap or touch (one ends exactly when the next starts), and returns them sorted by start. An empty list returns an empty list.","starter_files":[{"path":"scheduling/merge.py","content":"def merge(intervals):\n    result = []\n    for start, end in intervals:\n        if result and start < result[-1][1]:\n            result[-1] = (result[-1][0], max(result[-1][1], end))\n        else:\n            result.append((start, end))\n    return result\n"}],"solution_files":[{"path":"scheduling/merge.py","content":"def merge(intervals):\n    result = []\n    for start, end in sorted(intervals):\n        if result and start <= result[-1][1]:\n            result[-1] = (result[-1][0], max(result[-1][1], end))\n        else:\n            result.append((start, end))\n    return result\n"}],"test_files":[{"path":"tests/test_merge.py","content":"from scheduling.merge import merge\n\n\ndef test_sorted_overlaps():\n    assert merge([(1, 3), (2, 5), (7, 8)]) == [(1, 5), (7, 8)]\n\n\ndef test_contained_interval():\n    assert merge([(1, 10), (2, 3)]) == [(1, 10)]\n\n\ndef test_empty():\n    assert merge([]) == []\n\n\ndef test_unsorted_input():\n    assert merge([(7, 8), (1, 3), (2, 5)]) == [(1, 5), (7, 8)]\n\n\ndef test_touching_intervals_merge():\n    assert merge([(10, 11), (11, 12)]) == [(10, 12)]\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_merge.py::test_unsorted_input","tests/test_merge.py::test_touching_intervals_merge"],"pass_to_pass":["tests/test_merge.py::test_sorted_overlaps","tests/test_merge.py::test_contained_interval","tests/test_merge.py::test_empty"],"checked_at":"2026-09-29T23:30:19.388Z"}}
{"id":"code-06","title":"Order semantic versions by the spec","difficulty":"hard","language":"python","tags":["parsing","specifications"],"problem_statement":"versions/semver.py compares version strings for our update checker. It compares them as plain strings, so 1.10.0 sorts before 1.9.0, and pre-releases such as 1.0.0-beta are offered as newer than 1.0.0.\n\nImplement compare(a, b), returning -1, 0 or 1, following Semantic Versioning 2.0.0 precedence:\n- compare major, minor and patch numerically;\n- a pre-release (1.0.0-alpha) is lower than the same version without one;\n- pre-release identifiers are compared left to right: numeric ones numerically, others as ASCII text, numeric lower than text, and a longer list wins when all earlier identifiers are equal;\n- build metadata after \"+\" is ignored.","starter_files":[{"path":"versions/semver.py","content":"def compare(a: str, b: str) -> int:\n    return (a > b) - (a < b)\n"}],"solution_files":[{"path":"versions/semver.py","content":"def _parse(version: str):\n    version = version.split(\"+\", 1)[0]\n    core, _, pre = version.partition(\"-\")\n    major, minor, patch = (int(part) for part in core.split(\".\"))\n    return (major, minor, patch), pre.split(\".\") if pre else []\n\n\ndef _compare_identifiers(a: list[str], b: list[str]) -> int:\n    for x, y in zip(a, b):\n        if x == y:\n            continue\n        x_num, y_num = x.isdigit(), y.isdigit()\n        if x_num and y_num:\n            return (int(x) > int(y)) - (int(x) < int(y))\n        if x_num:\n            return -1\n        if y_num:\n            return 1\n        return (x > y) - (x < y)\n    return (len(a) > len(b)) - (len(a) < len(b))\n\n\ndef compare(a: str, b: str) -> int:\n    (core_a, pre_a), (core_b, pre_b) = _parse(a), _parse(b)\n    if core_a != core_b:\n        return (core_a > core_b) - (core_a < core_b)\n    if not pre_a and not pre_b:\n        return 0\n    if not pre_a:\n        return 1\n    if not pre_b:\n        return -1\n    return _compare_identifiers(pre_a, pre_b)\n"}],"test_files":[{"path":"tests/test_semver.py","content":"from versions.semver import compare\n\n\ndef test_equal():\n    assert compare(\"1.2.3\", \"1.2.3\") == 0\n\n\ndef test_major_versions():\n    assert compare(\"1.0.0\", \"2.0.0\") == -1\n\n\ndef test_numeric_not_lexical():\n    assert compare(\"1.10.0\", \"1.9.0\") == 1\n\n\ndef test_prerelease_is_lower_than_release():\n    assert compare(\"1.0.0-alpha\", \"1.0.0\") == -1\n\n\ndef test_prerelease_chain_from_the_spec():\n    chain = [\"1.0.0-alpha\", \"1.0.0-alpha.1\", \"1.0.0-alpha.beta\", \"1.0.0-beta\", \"1.0.0-beta.2\", \"1.0.0-beta.11\", \"1.0.0-rc.1\", \"1.0.0\"]\n    for lower, higher in zip(chain, chain[1:]):\n        assert compare(lower, higher) == -1, (lower, higher)\n        assert compare(higher, lower) == 1, (higher, lower)\n\n\ndef test_build_metadata_is_ignored():\n    assert compare(\"1.0.0+build.5\", \"1.0.0+build.9\") == 0\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_semver.py::test_numeric_not_lexical","tests/test_semver.py::test_prerelease_is_lower_than_release","tests/test_semver.py::test_prerelease_chain_from_the_spec","tests/test_semver.py::test_build_metadata_is_ignored"],"pass_to_pass":["tests/test_semver.py::test_equal","tests/test_semver.py::test_major_versions"],"checked_at":"2026-09-29T23:30:20.601Z"}}
{"id":"code-07","title":"Stop the rate limiter from over-refilling","difficulty":"medium","language":"python","tags":["concurrency limits","time"],"problem_statement":"limits/bucket.py implements a token bucket for our public API: rate tokens per second, up to capacity. Under steady traffic it lets far more requests through than configured, and after an idle period it allows unlimited bursts.\n\nFix TokenBucket.allow so that tokens refill only for the time since the previous call and never exceed capacity. Keep the constructor (rate, capacity, clock) and allow(cost=1) -> bool; tests inject a fake clock.","starter_files":[{"path":"limits/bucket.py","content":"import time\n\n\nclass TokenBucket:\n    def __init__(self, rate: float, capacity: int, clock=time.monotonic):\n        self.rate = rate\n        self.capacity = capacity\n        self.clock = clock\n        self.tokens = capacity\n        self.start = clock()\n\n    def allow(self, cost: int = 1) -> bool:\n        self.tokens += (self.clock() - self.start) * self.rate\n        if self.tokens >= cost:\n            self.tokens -= cost\n            return True\n        return False\n"}],"solution_files":[{"path":"limits/bucket.py","content":"import time\n\n\nclass TokenBucket:\n    def __init__(self, rate: float, capacity: int, clock=time.monotonic):\n        self.rate = rate\n        self.capacity = capacity\n        self.clock = clock\n        self.tokens = float(capacity)\n        self.last = clock()\n\n    def allow(self, cost: int = 1) -> bool:\n        now = self.clock()\n        self.tokens = min(self.capacity, self.tokens + (now - self.last) * self.rate)\n        self.last = now\n        if self.tokens >= cost:\n            self.tokens -= cost\n            return True\n        return False\n"}],"test_files":[{"path":"tests/test_bucket.py","content":"from limits.bucket import TokenBucket\n\n\nclass FakeClock:\n    def __init__(self):\n        self.now = 0.0\n\n    def __call__(self):\n        return self.now\n\n\ndef test_burst_up_to_capacity():\n    clock = FakeClock()\n    bucket = TokenBucket(rate=1, capacity=3, clock=clock)\n    assert [bucket.allow() for _ in range(4)] == [True, True, True, False]\n\n\ndef test_cost_above_one():\n    bucket = TokenBucket(rate=1, capacity=5, clock=FakeClock())\n    assert bucket.allow(cost=5)\n    assert not bucket.allow()\n\n\ndef test_refills_only_for_elapsed_time():\n    clock = FakeClock()\n    bucket = TokenBucket(rate=1, capacity=3, clock=clock)\n    for _ in range(3):\n        bucket.allow()\n    clock.now = 1.0\n    assert bucket.allow()\n    assert not bucket.allow()\n\n\ndef test_never_exceeds_capacity_after_idle():\n    clock = FakeClock()\n    bucket = TokenBucket(rate=10, capacity=3, clock=clock)\n    clock.now = 100.0\n    assert [bucket.allow() for _ in range(4)] == [True, True, True, False]\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-python:1","fail_to_pass":["tests/test_bucket.py::test_refills_only_for_elapsed_time","tests/test_bucket.py::test_never_exceeds_capacity_after_idle"],"pass_to_pass":["tests/test_bucket.py::test_burst_up_to_capacity","tests/test_bucket.py::test_cost_above_one"],"checked_at":"2026-09-29T23:30:21.792Z"}}
{"id":"code-08","title":"Add leading calls and cancel to debounce","difficulty":"medium","language":"typescript","tags":["timers","frontend"],"problem_statement":"debounce.ts powers search-as-you-type. Product wants two changes, and neither works today:\n\n1. With { leading: true } the first call in a burst runs immediately; later calls in the same burst still produce one trailing call with the latest arguments (unless { trailing: false }).\n2. The returned function has cancel(), which drops any pending call.\n\nA burst ends when wait milliseconds pass without a call. Keep the default behavior: trailing only.","starter_files":[{"path":"debounce.ts","content":"export function debounce<A extends unknown[]>(\n  fn: (...args: A) => void,\n  wait: number,\n  _options: { leading?: boolean; trailing?: boolean } = {},\n) {\n  let timer: ReturnType<typeof setTimeout> | undefined;\n  return (...args: A) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn(...args), wait);\n  };\n}\n"}],"solution_files":[{"path":"debounce.ts","content":"export function debounce<A extends unknown[]>(\n  fn: (...args: A) => void,\n  wait: number,\n  { leading = false, trailing = true }: { leading?: boolean; trailing?: boolean } = {},\n) {\n  let timer: ReturnType<typeof setTimeout> | undefined;\n  let pending: A | undefined;\n  const debounced = (...args: A) => {\n    if (timer === undefined && leading) {\n      fn(...args);\n      pending = undefined;\n    } else {\n      pending = args;\n    }\n    clearTimeout(timer);\n    timer = setTimeout(() => {\n      timer = undefined;\n      if (trailing && pending) fn(...pending);\n      pending = undefined;\n    }, wait);\n  };\n  debounced.cancel = () => {\n    clearTimeout(timer);\n    timer = undefined;\n    pending = undefined;\n  };\n  return debounced;\n}\n"}],"test_files":[{"path":"debounce.test.ts","content":"import assert from \"node:assert/strict\";\nimport { afterEach, beforeEach, mock, test } from \"node:test\";\nimport { debounce } from \"./debounce.ts\";\n\nbeforeEach(() => mock.timers.enable({ apis: [\"setTimeout\"] }));\nafterEach(() => mock.timers.reset());\n\ntest(\"calls once with the latest arguments after the wait\", () => {\n  const calls: number[] = [];\n  const d = debounce((n: number) => calls.push(n), 100);\n  d(1);\n  d(2);\n  d(3);\n  mock.timers.tick(99);\n  assert.deepEqual(calls, []);\n  mock.timers.tick(1);\n  assert.deepEqual(calls, [3]);\n});\n\ntest(\"leading runs the first call now and the latest call at the end\", () => {\n  const calls: number[] = [];\n  const d = debounce((n: number) => calls.push(n), 100, { leading: true });\n  d(1);\n  assert.deepEqual(calls, [1]);\n  d(2);\n  d(3);\n  mock.timers.tick(100);\n  assert.deepEqual(calls, [1, 3]);\n});\n\ntest(\"leading without trailing runs once per burst\", () => {\n  const calls: number[] = [];\n  const d = debounce((n: number) => calls.push(n), 100, { leading: true, trailing: false });\n  d(1);\n  d(2);\n  mock.timers.tick(100);\n  d(3);\n  assert.deepEqual(calls, [1, 3]);\n});\n\ntest(\"cancel drops the pending call\", () => {\n  const calls: number[] = [];\n  const d = debounce((n: number) => calls.push(n), 100);\n  d(1);\n  d.cancel();\n  mock.timers.tick(200);\n  assert.deepEqual(calls, []);\n});\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-node:1","fail_to_pass":["debounce.test.ts::leading runs the first call now and the latest call at the end","debounce.test.ts::leading without trailing runs once per burst","debounce.test.ts::cancel drops the pending call"],"pass_to_pass":["debounce.test.ts::calls once with the latest arguments after the wait"],"checked_at":"2026-09-29T23:30:22.725Z"}}
{"id":"code-09","title":"Close a prototype-pollution hole in deepMerge","difficulty":"hard","language":"typescript","tags":["security","objects"],"problem_statement":"merge.ts merges user settings (parsed from JSON) into defaults. A pentest showed that a settings payload of {\"__proto__\": {\"isAdmin\": true}} makes every object in the process report isAdmin === true. It also mutates the defaults object, so one user's settings leak into the next request, and arrays are merged index by index instead of replaced.\n\nRewrite deepMerge(target, source) so that it returns a new object without modifying either input, merges plain objects recursively, replaces arrays and other values from source, and skips the keys __proto__, constructor and prototype.","starter_files":[{"path":"merge.ts","content":"export function deepMerge(target: any, source: any): any {\n  for (const key in source) {\n    if (typeof source[key] === \"object\" && source[key] !== null) {\n      target[key] = deepMerge(target[key] ?? {}, source[key]);\n    } else {\n      target[key] = source[key];\n    }\n  }\n  return target;\n}\n"}],"solution_files":[{"path":"merge.ts","content":"type Plain = Record<string, unknown>;\n\nconst BLOCKED = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nfunction isPlain(value: unknown): value is Plain {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;\n}\n\nexport function deepMerge(target: Plain, source: Plain): Plain {\n  const out: Plain = {};\n  for (const key of Object.keys(target)) {\n    if (!BLOCKED.has(key)) out[key] = target[key];\n  }\n  for (const key of Object.keys(source)) {\n    if (BLOCKED.has(key)) continue;\n    const value = source[key];\n    if (isPlain(value)) out[key] = deepMerge(isPlain(out[key]) ? out[key] : {}, value);\n    else if (Array.isArray(value)) out[key] = [...value];\n    else out[key] = value;\n  }\n  return out;\n}\n"}],"test_files":[{"path":"merge.test.ts","content":"import assert from \"node:assert/strict\";\nimport { test } from \"node:test\";\nimport { deepMerge } from \"./merge.ts\";\n\ntest(\"merges nested objects\", () => {\n  assert.deepEqual(deepMerge({ a: { b: 1 } }, { a: { c: 2 } }), { a: { b: 1, c: 2 } });\n});\n\ntest(\"null in source replaces the value\", () => {\n  assert.deepEqual(deepMerge({ a: { b: 1 } }, { a: null }), { a: null });\n});\n\ntest(\"does not modify its inputs\", () => {\n  const defaults = { theme: { color: \"blue\" } };\n  const settings = { theme: { size: 14 } };\n  deepMerge(defaults, settings);\n  assert.deepEqual(defaults, { theme: { color: \"blue\" } });\n  assert.deepEqual(settings, { theme: { size: 14 } });\n});\n\ntest(\"arrays are replaced, not merged\", () => {\n  assert.deepEqual(deepMerge({ tags: [\"a\", \"b\"] }, { tags: [\"c\"] }), { tags: [\"c\"] });\n});\n\ntest(\"__proto__ in JSON cannot pollute objects\", () => {\n  try {\n    deepMerge({}, JSON.parse('{\"__proto__\": {\"isAdmin\": true}}'));\n    assert.equal(({} as { isAdmin?: boolean }).isAdmin, undefined);\n  } finally {\n    delete (Object.prototype as { isAdmin?: boolean }).isAdmin;\n  }\n});\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-node:1","fail_to_pass":["merge.test.ts::does not modify its inputs","merge.test.ts::arrays are replaced, not merged","merge.test.ts::__proto__ in JSON cannot pollute objects"],"pass_to_pass":["merge.test.ts::merges nested objects","merge.test.ts::null in source replaces the value"],"checked_at":"2026-09-29T23:30:23.648Z"}}
{"id":"code-10","title":"Parse compound durations like 1h30m","difficulty":"easy","language":"typescript","tags":["parsing"],"problem_statement":"duration.ts parses durations for our job scheduler config. Operators write \"1h30m\" or \"2d 4h\" and the scheduler refuses to start, because only single units such as \"90s\" are supported.\n\nExtend parseDuration so that it accepts one or more parts, each a whole number followed by ms, s, m, h or d, optionally separated by spaces, and returns the total in milliseconds. Anything else, including an empty string, throws RangeError.","starter_files":[{"path":"duration.ts","content":"const UNITS: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };\n\nexport function parseDuration(text: string): number {\n  const match = /^(\\d+)(ms|s|m|h|d)$/.exec(text.trim());\n  if (!match) throw new RangeError(`Invalid duration: \"${text}\"`);\n  return Number(match[1]) * UNITS[match[2]];\n}\n"}],"solution_files":[{"path":"duration.ts","content":"const UNITS: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };\nconst PART = /^(\\d+)(ms|s|m|h|d)/;\n\nexport function parseDuration(text: string): number {\n  let rest = text.replace(/\\s+/g, \"\");\n  if (!rest) throw new RangeError(`Invalid duration: \"${text}\"`);\n  let total = 0;\n  while (rest) {\n    const match = PART.exec(rest);\n    if (!match) throw new RangeError(`Invalid duration: \"${text}\"`);\n    total += Number(match[1]) * UNITS[match[2]];\n    rest = rest.slice(match[0].length);\n  }\n  return total;\n}\n"}],"test_files":[{"path":"duration.test.ts","content":"import assert from \"node:assert/strict\";\nimport { test } from \"node:test\";\nimport { parseDuration } from \"./duration.ts\";\n\ntest(\"single units\", () => {\n  assert.equal(parseDuration(\"90s\"), 90_000);\n  assert.equal(parseDuration(\"250ms\"), 250);\n});\n\ntest(\"rejects invalid input\", () => {\n  for (const bad of [\"\", \"5x\", \"h1\", \"1.5h\"]) assert.throws(() => parseDuration(bad), RangeError, bad);\n});\n\ntest(\"compound durations\", () => {\n  assert.equal(parseDuration(\"1h30m\"), 5_400_000);\n  assert.equal(parseDuration(\"1m30s\"), 90_000);\n});\n\ntest(\"spaces between parts\", () => {\n  assert.equal(parseDuration(\"2d 4h\"), 187_200_000);\n});\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-node:1","fail_to_pass":["duration.test.ts::compound durations","duration.test.ts::spaces between parts"],"pass_to_pass":["duration.test.ts::single units","duration.test.ts::rejects invalid input"],"checked_at":"2026-09-29T23:30:24.558Z"}}
{"id":"code-11","title":"Keep once-listeners from skipping others","difficulty":"medium","language":"javascript","tags":["events","iteration"],"problem_statement":"emitter.js is a small event emitter used by our job runner. Two bugs:\n\n1. When a once() listener runs, the listener registered right after it is skipped for that event.\n2. off(event, fn) cannot remove a listener that was added with once(event, fn).\n\nFix both without changing the API: on() returns an unsubscribe function, once() registers a listener that runs at most once, off() removes a listener, emit() calls the listeners registered at the moment of the call.","starter_files":[{"path":"emitter.js","content":"export class Emitter {\n  #listeners = new Map();\n\n  on(event, fn) {\n    const list = this.#listeners.get(event) ?? [];\n    list.push(fn);\n    this.#listeners.set(event, list);\n    return () => this.off(event, fn);\n  }\n\n  once(event, fn) {\n    const wrapper = (...args) => {\n      this.off(event, wrapper);\n      fn(...args);\n    };\n    return this.on(event, wrapper);\n  }\n\n  off(event, fn) {\n    const list = this.#listeners.get(event);\n    if (!list) return;\n    const i = list.indexOf(fn);\n    if (i >= 0) list.splice(i, 1);\n  }\n\n  emit(event, ...args) {\n    for (const fn of this.#listeners.get(event) ?? []) fn(...args);\n  }\n}\n"}],"solution_files":[{"path":"emitter.js","content":"export class Emitter {\n  #listeners = new Map();\n\n  on(event, fn) {\n    const list = this.#listeners.get(event) ?? [];\n    list.push(fn);\n    this.#listeners.set(event, list);\n    return () => this.off(event, fn);\n  }\n\n  once(event, fn) {\n    const wrapper = (...args) => {\n      this.off(event, wrapper);\n      fn(...args);\n    };\n    wrapper.original = fn;\n    return this.on(event, wrapper);\n  }\n\n  off(event, fn) {\n    const list = this.#listeners.get(event);\n    if (!list) return;\n    const i = list.findIndex((l) => l === fn || l.original === fn);\n    if (i >= 0) list.splice(i, 1);\n  }\n\n  emit(event, ...args) {\n    for (const fn of [...(this.#listeners.get(event) ?? [])]) fn(...args);\n  }\n}\n"}],"test_files":[{"path":"emitter.test.js","content":"import assert from \"node:assert/strict\";\nimport { test } from \"node:test\";\nimport { Emitter } from \"./emitter.js\";\n\ntest(\"on and emit\", () => {\n  const e = new Emitter();\n  const seen = [];\n  e.on(\"job\", (id) => seen.push(id));\n  e.emit(\"job\", 7);\n  assert.deepEqual(seen, [7]);\n});\n\ntest(\"unsubscribe returned by on\", () => {\n  const e = new Emitter();\n  const seen = [];\n  const stop = e.on(\"job\", (id) => seen.push(id));\n  stop();\n  e.emit(\"job\", 1);\n  assert.deepEqual(seen, []);\n});\n\ntest(\"once runs a single time\", () => {\n  const e = new Emitter();\n  let count = 0;\n  e.once(\"job\", () => count++);\n  e.emit(\"job\");\n  e.emit(\"job\");\n  assert.equal(count, 1);\n});\n\ntest(\"once does not skip the next listener\", () => {\n  const e = new Emitter();\n  const seen = [];\n  e.once(\"job\", () => seen.push(\"first\"));\n  e.on(\"job\", () => seen.push(\"second\"));\n  e.emit(\"job\");\n  assert.deepEqual(seen, [\"first\", \"second\"]);\n});\n\ntest(\"off removes a once listener\", () => {\n  const e = new Emitter();\n  let count = 0;\n  const listener = () => count++;\n  e.once(\"job\", listener);\n  e.off(\"job\", listener);\n  e.emit(\"job\");\n  assert.equal(count, 0);\n});\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-node:1","fail_to_pass":["emitter.test.js::once does not skip the next listener","emitter.test.js::off removes a once listener"],"pass_to_pass":["emitter.test.js::on and emit","emitter.test.js::unsubscribe returned by on","emitter.test.js::once runs a single time"],"checked_at":"2026-09-29T23:30:25.326Z"}}
{"id":"code-12","title":"Split a bill in cents without losing a cent","difficulty":"easy","language":"javascript","tags":["money","rounding"],"problem_statement":"split.js divides a bill between people by weight (for example nights stayed). Finance found that splits often add up to a cent more or less than the bill.\n\nRewrite splitCents(totalCents, weights) so that it returns whole cents that always add up to totalCents exactly. Give each person the floor of their exact share, then hand out the remaining cents one at a time to the largest fractional remainders, earliest person first on ties. Throw RangeError when totalCents is not a non-negative integer, weights is empty, or any weight is negative, or all are zero.","starter_files":[{"path":"split.js","content":"export function splitCents(totalCents, weights) {\n  const sum = weights.reduce((a, b) => a + b, 0);\n  return weights.map((w) => Math.round((totalCents * w) / sum));\n}\n"}],"solution_files":[{"path":"split.js","content":"export function splitCents(totalCents, weights) {\n  if (!Number.isInteger(totalCents) || totalCents < 0) throw new RangeError(\"totalCents must be a non-negative integer\");\n  const sum = weights.reduce((a, b) => a + b, 0);\n  if (!weights.length || sum <= 0 || weights.some((w) => w < 0)) throw new RangeError(\"weights must be non-negative and not all zero\");\n  const exact = weights.map((w) => (totalCents * w) / sum);\n  const shares = exact.map(Math.floor);\n  let left = totalCents - shares.reduce((a, b) => a + b, 0);\n  const order = exact.map((x, i) => ({ i, remainder: x - Math.floor(x) })).sort((a, b) => b.remainder - a.remainder || a.i - b.i);\n  for (const { i } of order) {\n    if (left === 0) break;\n    shares[i] += 1;\n    left -= 1;\n  }\n  return shares;\n}\n"}],"test_files":[{"path":"split.test.js","content":"import assert from \"node:assert/strict\";\nimport { test } from \"node:test\";\nimport { splitCents } from \"./split.js\";\n\ntest(\"even split\", () => {\n  assert.deepEqual(splitCents(900, [1, 1, 1]), [300, 300, 300]);\n});\n\ntest(\"weighted split\", () => {\n  assert.deepEqual(splitCents(1001, [2, 1]), [667, 334]);\n});\n\ntest(\"always adds up to the bill\", () => {\n  assert.deepEqual(splitCents(1000, [1, 1, 1]), [334, 333, 333]);\n});\n\ntest(\"remainders go to the earliest people on ties\", () => {\n  assert.deepEqual(splitCents(100, [1, 1, 1, 1, 1, 1]), [17, 17, 17, 17, 16, 16]);\n});\n\ntest(\"rejects bad input\", () => {\n  assert.throws(() => splitCents(100, []), RangeError);\n  assert.throws(() => splitCents(100, [0, 0]), RangeError);\n  assert.throws(() => splitCents(10.5, [1]), RangeError);\n});\n"}],"checks":{"status":"passed","runner":"canonset-sandbox-node:1","fail_to_pass":["split.test.js::always adds up to the bill","split.test.js::remainders go to the earliest people on ties","split.test.js::rejects bad input"],"pass_to_pass":["split.test.js::even split","split.test.js::weighted split"],"checked_at":"2026-09-29T23:30:26.082Z"}}
