If you are working with Typescript you must know what utility types are and how much value they bring.
Stay with me in this article as we will go through Pick, Omit, Reduce and Partial. At the end of the video I’ll provide a bonus typescript tip that you don’t want to miss! Enjoy.
Video Version
The reason why you choose to work with typescript is its huge value added to the vanilla javascript language, writing less error prone code and identifying production bugs before they happen.
I worked with javascript for 10 years now and been using typescript for more than 5. I would never use vanilla js ever again. Typescript brings so much value to this language that it’s a no brainer to use it. If you don’t agree, let me know in the comments below and we can discuss. In the end it’s just an opinion, not a universal truth and it shouldn’t be.
Let’s set the scene and explain utility types using a proper example: User account creation & allowing profile editing
For a user to create an account we need a UI form and a server endpoint for creating the user. After the client creates his account, we allow editing the profile.

interface UserI {
id: string;
email: string;
password: string;
first_name: string;
middle_name: string;
last_name: string;
date_of_birth: string;
phone: string;
}
We share the same interface between server and frontend so that we don’t duplicate code. We already have a small problem, we can’t use this user interface for the user creation frontend nor for backend, since the form doesn’t have an id field.
Our signup form has 2 steps:
- Step 1: email, password, confirm_password
- Step 2: first_name, middle_name, last_name, date_of_birth, phone
Of course, we can manually define the types for both frontend and server but this means duplicated code and hard to maintain, which is a bad idea.
This is where the typescript utility types come into play and bring lots of value in
1. Pick<Type, Keys> utility type
The not recommended option: re-define manually the form types considering the user interface type.
// step 1
interface Step1Interface {
email: string;
password: string;
confirm_password: string;
}
// step 2 form type
interface Step2Interface {
first_name: string;
middle_name: string;
last_name: string;
date_of_birth: string;
phone: string;
}
We can totally do it by defining new types like in the above example. But there is a big problem – we are duplicating code (we break the DRY principle – don’t repeat yourself). In the end, the backend will validate against the user interface above.
If the user interface changes (e.g. middle_name becomes optional and phone gets removed) that cannot be reflected. Changing those will never show as error in this form and they should.
As a rule of thumb, we should never re-define a type manually from scratch if it is related / linked to another in a certain way.
This is where Pick comes into play and brings value. We can use it to create a new type based on the user interface, hence having the link between them and being warned if we do a breaking change.
Pick returns a new type by picking only a specific list of attributes from another type.
// with Pick
// returns a new type having only these attributes from the UserI
type Step1Interface = Pick<UserI, "email" | "password"> & {confirm_password: string};
type Step2Interface = Pick<UserI, "first_name" | "middle_name" | "last_name" | "date_of_birth" | "phone">;
/**
* Keys -> can be an attribute (e.g. "id") or a list of attributes ("id" | "first_name")
*/
Not only that this is clean, but it will also error in case we are trying to pick an attribute that doesn’t exist anymore (e.g. middle_name gets removed from UserInterface).

We can either pick one or more attributes. In case of multiple attributes we need to use the | to mention the list of attributes.
2. Omit<Type, Keys> utility type
In opposition as Pick, Omit returns a new type derived from the original interface, having all attributes except the ones we explicitly specify.
// with Omit
type Step1Interface = Omit<UserI, "id" | "first_name" | "middle_name" | "phone" | "last_name" | "date_of_birth"> & {confirm_password: string};
type Step2Interface = Pick<UserI, "id" | "email" | "password">;
/**
* Keys -> can be an attribute (e.g. "id") or a list of attributes ("id" | "first_name")
*/
3. Partial<Type> utility type
Now that the user created his account, we need our client to be able to edit his profile. We display the same form and we decide to send to the server only the changed fields.
Server side API needs a flexible enough type, not knowing in advance which fields the client edited. This is where Partial comes into play.
Using Partial returns a new type derived from User Interface which has all attributes optional.
// Partial example
type UserEditInterface = Partial<UserI>;
// equivalent of
type UserEditInterface = {
id?: string;
email?: string;
password?: string;
first_name?: string;
middle_name?: string;
last_name?: string;
date_of_birth?: string;
phone?: string;
}
4. Record<Keys, Type>
Alternatively to the Partial example above, we can use Record.
It returns a new type defining an object where you can generically specify the key type and its value type.
type UpdateProfileType = Record<string, string>
Bonus Did-You-Know Tip – How to get an interface’s attribute type
Having the following interface
interface UserI {
id: string;
address: {
zip: number;
street: {
name: string;
number: number;
};
};
}
How can we get the user’s address street type without redefining it ?
We use the object syntax on interfaces which instead of returning a value, it returns a type.
let street: UserI['address']['street'] = ...
// or
type StreetType = UserI['address']['street']
We’ll stop here for this article, I hope it was helpful and easy to follow.
Did you find this useful ? Let me know in the comment section below!
Stay tuned, video version coming soon as well.
Have you also checked out our serverless framework introduction with typescript ? You can find it here.