DEV Community

Muhamad Ilyas Mustafa
Muhamad Ilyas Mustafa

Posted on

Understanding the "Infer" Keyword in String Literal Types

When working with string literal types in TypeScript, the use of the infer keyword can lead to some intriguing behaviors. Let's delve into an example to explore its nuances:

type Test<T> = T extends `${infer A}${infer B}${infer C}`
  ? `${A}-${B}-${C}`
  : never;
type Test1 = Test<"a">; // never
type Test2 = Test<"ab">; // a-b-
type Test3 = Test<"abcd">; // a-b-cd
Enter fullscreen mode Exit fullscreen mode

Observations:

  • The first result is never because the string literal type is only one character long, and our type requires at least three characters.
  • The second result, a-b-, may seem unexpected. The infer keyword is greedy, attempting to match as many characters as possible. In this case, it matches a to A, b to B, and the remaining empty string to C. As we can only have one empty string in the template literal type, the result is a-b-. This behavior becomes clearer with a four-character example:
type Test4Char<T> = T extends `${infer A}${infer B}${infer C}${infer D}`
  ? `${A}-${B}-${C}-${D}`
  : never;
type Test1 = Test4Char<"a">; // never (B, C, and D are empty strings, and only one is allowed)
type Test2 = Test4Char<"ab">; // never (C & D are empty strings, and only one is allowed)
type Test3 = Test4Char<"abc">; // a-b-c- (D is empty string)
type Test4 = Test4Char<"abcd">; // a-b-c-d
Enter fullscreen mode Exit fullscreen mode
  • The third result, a-b-cd, demonstrates that C matches cd, storing the remaining string in C.

With this understanding, let's leverage this knowledge to create some intriguing types:

// Get the last character of a string
type LastChar<T extends string> = T extends `${infer A}${infer Last}`
  ? Last extends ""
    ? A
    : LastChar<Last>
  : never;
type TestL1 = LastChar<"abcd">; // 'd'

// Reverse a string
type Reverse<T extends string> = T extends `${infer A}${infer B}`
  ? `${Reverse<B>}${A}`
  : T;
type TestR1 = Reverse<"reverse">; // esrever

// Convert camel case to kebab case
type CamelToKebab<T> = T extends `${infer A}${infer B}`
  ? `${A extends Capitalize<A> ? `-${Lowercase<A>}` : A}${CamelToKebab<B>}`
  : T;

type TestC1 = CamelToKebab<"fooBarBaz">; // foo-bar-baz
type TestC2 = CamelToKebab<"fooBar">; // foo-bar
type TestC3 = CamelToKebab<"foo">; // foo
Enter fullscreen mode Exit fullscreen mode

That's all for now. Happy typing!

Top comments (0)