Creating Utility Types in TypeScript

Sahin Deniz
Insider Engineering
4 min readJun 17, 2024
TS Utility Types

TypeScript, a superset of JavaScript, offers static type-checking and a robust type system. One of its powerful features is generics, which provides a way to create reusable and flexible functions/types.

Combined with generics, utility types can enhance code reusability and type safety. In this article, we’ll see some examples of creating utility types using TypeScript generics.

Understanding Generics

Generics allow you to define a type that can be reused with different types. They provide a way to create a component that can work with any data type, without sacrificing type safety.

Simple Example

Consider a scenario where we need the type of values of properties of an object.

type ValueOf<TargetObject extends {}> = TargetObject[keyof TargetObject];

When looking at this type of declaration, try to see it as a function. It accepts a generic parameter called TargetObject and receives values that inherit the type of {}. This means the TargetObject can be anything that extends an Object.
If we separate the parts of assignment into two parts

keyof TargetObject : The keyof statement is to retrieve the keys of an object.
Let's say we have { foo: 1, bar: 'hello' } the statement will extract the keys and return a new type with 'foo' | 'bar'.

TargetObject[..] This statement is similar to accessing the property of an object.
The difference is that it accepts a type instead of a string key.
For example:

const myObject = { foo: 1, bar: 'hello' };

const barValue = myObject['foo']; // similar to TargetObject[..]

Combining both parts

TargetObject[keyof TargetObject] will be evaluated as TargetObject['foo' | 'bar'] and since we typed it to access the types of values, it will return a union type of properties. Which is string | number .

If we mistakenly assign a value that is not present in the object properties, we will get an error.

type Value = ValueOf<{ foo: 1, bar: 'hello' }>; 

const myValue: Value = true;
// Type 'true' is not assignable to type 'Value'.

Of course, it is not recommended to use {} to denote an object. Instead, we can use something like Record<string, unknown> or anything that resembles an object.

String Manipulation

By using generics, we can manipulate literal string types.

Let’s say we have a variable that holds a name and its type will be a literal string. The importance is that the type is not actually string but 'sahin', that’s why I’m saying literally :)

const firstName = 'sahin';

Now, we want the first character to be capitalized.

function capitalizeFirstLetter(text: string) {
return text.charAt(0).toUpperCase() + text.slice(1);
}

const capitalizedName = capitalizeFirstLetter(firstName);

The first character will get capitalized at runtime, but at the type level, it just lost the literal type of name variable, and it has string as type.

Q: How do I solve this at the type level?

Let's create a utility type that takes a string argument and returns it with the capitalized version of it.

type CapitalizeFirstLetter<Text extends string> =
Text extends `${infer FirstChar}${infer RestOfTheString}`
? `${Capitalize<FirstChar>}${RestOfTheString}`
: never;

Let's tweak the function with generics:

function capitalizeFirstLetter<Value extends string>(text: Value) {
return (
text.charAt(0).toUpperCase() + text.slice(1)
) as CapitalizeFirstLetter<Value>;
}

Now we can use it

const capitalizedName = capitalizeFirstLetter(firstName);

Here’s the TypeScript Playground link

String with Auto-Completion

Let’s take this a bit further.

Say you have a use case that you need to specify the type of variable string, but at the same time, you need to get suggestions for specific literal string type.

In the following example, you’ll get a type error, saying Type “sahin” is not assignable to type ‘Name’.

type Name = 'john' | 'jane';

const firstName: Name = 'sahin';

This is quite alright because we simply restricted the type of firstName to type Name.

Now let's create a utility type that makes the assignment possible but still gets auto-completion suggestions on your editor

type StringGeneric<Text> = Text | (string & Record<never, never>);

I call this StringGeneric because it creates a generic string, literally 😁.

Let’s see it in action:

Edit on TypeScript Playground.

Useful links

Built-in utility types:
https://www.typescriptlang.org/docs/handbook/utility-types.html

Generics:
https://www.typescriptlang.org/docs/handbook/2/generics.html

If you want insight into how to debug effectively on Google Chrome, take a look at the Effective Debugging post by Ugurcan Ertek, Software Engineer at Insider.

Also, don’t forget to follow the Insider Engineering Blog to see more articles ✍️

You can reach me on LinkedIn 📥

--

--