Awesome question — this gets right into the heart of
how TypeScript handles generics in template
literals
when you don't constrain them.
💡 Quick answer:
If you
don’t
write
S extends string
, then TypeScript treats
S
and
T
as
generic types
— which might not be string-compatible.
And when used like this:
TypeScript says:
“Wait — I don’t know what
S
is.
It could be anything — number? object? array? Maybe
it's a weird intersection like
T & S
.”
And unless it's
guaranteed
to be a string-compatible primitive (string | number | boolean | bigint | null |
undefined
), it
throws an error.
🧠 Deeper explanation: why
T & S
appears
Let’s imagine we define:
Now inside this recursive type, TypeScript sometimes
needs to resolve both
S
and
T
in a
template literal like:
At this point:
-
TypeScript doesn't yet know that
S
is
a string.
-
And
T
is inferred as the full original union.
-
So when it evaluates the substitution types, it
could infer
S
and
T
as
intersecting types
(due to internal inference or type operations).
Thus, it might treat
${S}
as
${T & S}
in internal calculations — which may not be valid for
template string use.
And that's why you get this error:
Type 'T & S' is not assignable to type
'string | number | bigint | boolean | null |
undefined'.
✅ Solution: Constrain your generic
Now you're telling TypeScript:
“Hey — S and T are definitely strings (or string
literals). You can safely use them inside template
strings.”
So
${S}
and
${S}-${...}
are
totally valid now, and the error goes away.
🧪 Real example
Try this in a playground:
Without
extends string
, TypeScript will not assume it can safely coerce
S
into
a string —
even if you think it’s clear.
✅ TL;DR
Let me know if you want a visual diagram of how
TypeScript expands and infers the types inside
recursive templates — I can draw out the whole mental
model for you.