How does TypeScript infer U in the following code?
type ExtractPrefix<T> = T extends `${infer U}abc` ? U : never;
const mystring = "helloabc"
type Result = ExtractPrefix<typeof mystring>; // Result is 'hello'
Why does ${infer U}abc extract only "hello"?
Also, if I change the pattern to ${infer U}c, the result becomes "helloab". How does TypeScript determine where to stop inferring U?
I’m struggling to understand how infer U works. Could someone clarify?
TypeScript checks to see if the type parameter, T
, extends the template literal type. Here is a simpler example without infer
:
type CheckPrefix<T> = T extends `${string}abc` ? true : never;
Here, you are telling Typescript to check if T
ends with abc
. You can use this pattern to check any substring. For example, you could use the template literal type ${string}abc${string}xyz
to check if a string literal type contains abc
and ends with xyz
. Note that an empty string (""
) is valid type, so "abcxyz"
would still satisfy ${string}abc${string}xyz
.
Typescript checks whether T
can be split into some string U
followed by the literal "abc"
. Since "helloabc"
ends with "abc"
, Typescript gives you the true branch on the conditional— which is U
, the rest of T
. This is why "hello"
is extracted from ${infer U}abc
.
Typescript checks that the literal part(s) of the type (e.g. "abc"
or "c"
) exactly matches T
. The compiler "stops" inferring U
when the next literal part of the type occurs. When you change the literal part of your type to "c"
, the "rest" of the type is "helloab"
. This is then inferred as the type U
, which is your result in the true branch.
It's also worth noting that you can use extends
inside the infer
. For example:
type TLD = 'gov' | 'edu' | 'com' | 'net' | 'dev';
type Domain = `${string}.${TLD}`;
type DomainForEmail<T> = T extends `${string}@${infer TDomain extends Domain}` ? TDomain : never;
type _ = DomainForEmail<'john@example.com'>; // "example.com"
Template Literal Types has a lot of information on this and is worth a read.