Skip to content
webtype.orgwebtype.org

    ↑↓ move · ⏎ open · esc close

    No. 252 · September 4, 2026 · Brutal

    Query string

    Implement ParseQuery<S> so it turns a query string like "a=1&b=2" into { a: "1"; b: "2" }. An empty string produces {}. The result must be one flat object; Flatten is provided.

    01

    Try the puzzle yourself

    Par 5

    Puzzle

    parse-query.ts
    type Flatten<T> = { [K in keyof T]: T[K] }
    Stroke 1 of 5Not run yet

    Replace ??? — your solution is checked against the cases below. Tab indents; press Escape then Tab to move focus out.

    Checks

    4
    • ParseQuery<'a=1&b=2'>—
      → { a: '1'; b: '2' }
    • ParseQuery<'x=9'>—
      → { x: '9' }
    • ParseQuery<''>—
      → {}
    • ParseQuery<'a=1&b=2&c=3'>—
      → { a: '1'; b: '2'; c: '3' }

    How a check is judged Exact type equality, not assignability — an intersection is not the same as the flattened object.

    How everyone did

    Fewer than 5 people have solved this one so far. The distribution appears once there is enough of a sample to mean anything.

    Short game

    Fewest characters

    No public scores yet.

    Archive
    02

    Annotated solution

    Published September 5, 2026

    The solution

    type ParseQuery<S extends string> =
      S extends `${infer K}=${infer V}&${infer Rest}`
        ? Flatten<{ [P in K]: V } & ParseQuery<Rest>>
        : S extends `${infer K}=${infer V}`
          ? { [P in K]: V }
          : {}

    The common wrong answer

    type ParseQuery<S extends string> =
      S extends `${infer K}=${infer V}&${infer Rest}`
        ? { [P in K]: V } & ParseQuery<Rest>
        : S extends `${infer K}=${infer V}`
          ? { [P in K]: V }
          : {}

    The parsing is entirely correct; the assembly is not. Every recursive step contributes one more &, so three pairs produce { a: "1" } & { b: "2" } & { c: "3" } — a chain of three objects that behaves like the one the check wants but is not it. Recursion that builds objects almost always needs flattening at the end.

    Line by line

    1. `${infer K}=${infer V}&${infer Rest}`

      Template literal inference matches from the left and takes the shortest match, so K is "a", V is "1", and everything after the first & becomes Rest. One pair per step.

    2. { [P in K]: V }

      K arrives as a string literal type, which is a perfectly good key set for a mapped type of exactly one member. This is how a parsed name becomes a real property.

    3. Flatten<... & ParseQuery<Rest>>

      Flattening at every step rather than only at the end keeps the intersection from ever getting deep, and the result of each step is already the shape the next one expects.

    Takeaway

    Parsing a string at the type level is three questions: does it have a separator, is it a bare final item, or is it empty? Get those three branches in the right order and the recursion writes itself.

    Uses