Annotated solution
Published September 5, 2026The 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
`${infer K}=${infer V}&${infer Rest}`Template literal inference matches from the left and takes the shortest match, so
Kis"a",Vis"1", and everything after the first&becomesRest. One pair per step.{ [P in K]: V }Karrives 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.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.

