5 min read
Frontend
TypeScript "infer" Tips
Read the summary
Using TypeScript’s infer lets you extract specific parts of a type—like the first element of a tuple or the first character of a string—without writing long, messy code. This feature works inside Conditional Types, which act like type-level if-else statements. For example, if a type can be assigned to a literal, it’s treated as true; otherwise, it’s false. The extends keyword here checks assignability, not inheritance. With infer, you define a placeholder type that gets resolved during type checking. For instance, (infer U)[] pulls out the element type from an array. If T is number[], the result is number. This lets you build custom utility types that mirror built-in ones like ReturnType and Parameters. You can also get the last element of a tuple using [...infer _, infer Last], which works because TypeScript doesn’t allow direct indexing like tuple[length-1]. This pattern is especially useful when you need to extract the final piece of a structured type. For strings, infer helps extract the first character with ${infer First}${string}, or capitalize the first letter using Uppercase on the inferred part. You can even trim leading and trailing spaces by recursively applying a Trim type using infer and a helper type. Overall, infer makes type manipulation more flexible and readable. While it takes practice to master, it reduces code bloat and improves type safety. When used correctly, it helps keep types concise, maintainable, and effective—without adding unnecessary complexity.
This summary was generated by AI. Please refer to the article for accurate details.
This article is part of day 2 of KINTO Technologies Advent Calendar 2024.
Introduction
Hello!
This is high-g (@high_g_engineer) from the New Car Subscription Development Group at the Osaka Tech Lab.
Recently, solving TypeScript type puzzles, known as type-challenges, has become part of my daily morning routine before work.
In this article, I will introduce some infer tips in the TypeScript type system that are a little quirky.
First, before explaining infer, I will explain Conditional Types, which are essential for using infer.
What are Conditional Types?
Conditional Types, also called code branching, are features of the type system that allow conditional branching at the type level.
I'd like to demonstrate an example of Conditional Types, as shown below:
type ConditionalTest<A> = A extends 'a' ? true : false
The right-hand side above has the following meaning:
- If A type can be assigned to a literal type 'a', it is a true type
- If A type is other than the above, it is a false type
It behaves like the conditional operator in general programming languages.
By the way, the extends keyword here has a different meaning than inheritance in general object-oriented programming.
In this case, extends becomes the keyword to check the type's assignability.
What is infer?
infer is only available in Conditional Types, introduced in TypeScript 2.8.
I'd like to demonstrate an example of Conditional Types, as shown below:
type InferTest<T> = T extends (infer U)[] ? U : never
On the right-hand side, we utilize Conditional Types, and to the right of the extends keyword, we specify the type we aim to obtain with infer.
In the case of the above type, if the T type is "an array of any element type," it means that the type of the element is returned.
(Note: Here "never" is the type returned if the conditions are not met.) (infer U)[] represents an array of any element type, so any array types such as string[], number[], boolean[] are applicable.
So if the T type was number[], the type resolution would be as follows.
type Result = InferTest<number[]> // number
This is just an example, but there are many other ways to use infer.
Functional manipulation using infer
To get the return type
const foo = (): string => 'Hello, TS!!'
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type FunctionReturn = MyReturnType<typeof foo> // string
Write the Conditional Types as before, and specify the desired type to the right of the extends keyword
This time, I want to extract the return type, so I place the function type to the right of extends and infer the return value.
This is the same behavior as TypeScript's built-in utility type ReturnType<T>.
When retrieving the type of the argument
const foo = (arg1: string, arg2: number): void => {}
type MyParameters<T> = T extends (...args: infer Arg) => any ? Arg : never
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
Since the arguments are tuple types, the rest parameter (spread syntax) can be used to handle any number of arguments.
You can extract types by describing Conditional Types + the type you want to get + infer.
This is the same behavior as TypeScript's built-in utility type Parameters <T>.
Manipulation of array types (tuple types) using infer
If you want to get the last element
When retrieving the type of the first element of a tuple type, the type definition is as follows:
type Tuple = [number, '1', 100]
type GetType = Tuple[0] // number
However, if you want to get the type of the last element of a tuple type, you cannot write Tuple[length-1] in TypeScript.
The best solution is infer. The type definition is as follows:
type ArrayLast<T> = T extends [...infer _, infer Last] ? Last : never
[...infer _, infer Last] extracts the last element type as Last if the T type is an array or tuple type.
type Test1 = ArrayLast<[1, 2, 3]> // 3
type Test2 = ArrayLast<[string, number, boolean]> // boolean
type Test3 = ArrayLast<[]> // never
Manipulating literal types using infer
To get the type of the first character of a literal type
type LiteralFirst<T extends string> = T extends `${infer First}${string}` ? First : never
${infer First}${string} extracts the beginning character of the string as First and treats the rest as string.
To obtain a literal type with the first letter capitalized
type FirstToUpper<T extends string> = T extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : never
As before, the string is processed separately, and Uppercase<First> uses a utility type to convert the first letter to uppercase and combine it with the rest of the string. Returns the never type if the string is empty.
To trim spaces, newline characters, and similar elements from the beginning and end of a literal type.
type Space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${Space}${infer T}` | `${infer T}${Space}` ? Trim<T> : S;
If you define a type called Space that includes a space character and a newline character, you can use Conditional Types to recursively apply the Trim type, effectively removing spaces from the beginning and end of the string.
Conclusion
As shown in these examples, using infer enables you to extract specific parts from a given type, significantly enhancing flexibility in defining and working with types.
It's a bit unconventional, so it may take some time to adapt, but it's an incredibly useful feature.
Using types enables robust development; however, incorrectly representing types can lead to the following risks:
- Poor code readability due to unnecessary type definitions
- Increased maintenance costs due to unnecessarily complex type definitions
- Reduced type safety
By effectively leveraging TypeScript type systems, such as infer, you can achieve concise and clear type expressions while consistently focusing on enhancing development productivity and quality.
関連記事 | Related Posts
We are hiring!
【オープンポジション】「気になる!」方はまずはこちらからご応募ください。/東京・名古屋・大阪・福岡
業務内容国内外のKINTOサービスや、トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、ご経験・ご志向性に応じて配属を決定し、ご活躍いただきます。
シニア/フロントエンドエンジニア(React/Typescript)/KINTO中古車開発G/東京・大阪・福岡
KINTO開発部KINTO中古車開発グループについて◉KINTO開発部 :66名 KINTO中古車開発G:9名★ KINTOプロダクトマネジメントG:3名 KINTOバックエンド開発G:16名 契約管理開発G :9名 KINTO開発推進G:8名 KINTOフロントエンド開発G:...
Follow Us
KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。
