Skip to content
#245 object-paths · par 5

    ↑↓ move · ⏎ open · esc close

    No. 245 · August 28, 2026 · Brutal

    Paths

    Implement `Paths<T>` so it produces the union of every dotted path into a nested object — including the intermediate ones, not just the leaves.

    01

    Try the puzzle yourself

    Par 5

    Puzzle

    object-paths.ts
    interface Config {
      db: { host: string; port: number }
      debug: boolean
    }
    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
    • Paths<Config>
      'db' | 'db.host' | 'db.port' | 'debug'
    • Paths<{ a: string }>
      'a'
    • Paths<{ x: { y: { z: 1 } } }>
      'x' | 'x.y' | 'x.y.z'
    • Paths<{}>
      never

    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 August 29, 2026

    The solution

    type Paths<T> = T extends object
      ? {
          [K in keyof T & string]: T[K] extends object
            ? K | `${K}.${Paths<T[K]> & string}`
            : K
        }[keyof T & string]
      : never

    The common wrong answer

    type Paths<T> = T extends object
      ? {
          [K in keyof T & string]: T[K] extends object
            ? `${K}.${Paths<T[K]> & string}`
            : K
        }[keyof T & string]
      : never

    This yields only the leaves: `"db.host" | "db.port" | "debug"`. The branch for an object-valued key returns the prefixed descendants but forgets that the key itself is a valid path too. The `K |` is doing the entire job of making intermediate paths appear.

    Line by line

    1. }[keyof T & string]

      The mapped type is scaffolding, never the answer. Indexing it by all its own keys reads out every value at once and unions them — the standard way to turn a per-key computation into a union.

    2. Paths<T[K]> & string

      The intersection is not decoration. A template literal will only interpolate something the compiler already knows is string-like, and a deferred recursive call is not — `& string` is the promise that lets it through.

    3. keyof T & string

      Object keys can be symbols, which cannot appear in a path string. Intersecting with `string` filters them out at both the mapping and the indexing step, and the two must agree or the result collapses.

    Takeaway

    A mapped type indexed by its own keys is the type-level equivalent of `Object.values().flat()`. Combined with recursion it walks arbitrarily deep structures — and this exact shape is how typed form libraries and i18n key checkers are built.

    Uses