TypeScript Utility Types
Explore TypeScript's built-in Utility Types like Partial, Required, Readonly, Record, Pick, and Omit with practical examples.
Hello! Typescript has a whole bunch of what are called “Utility Types”. These are specific types that allow you do some unique manipulations or transformations. They are built around reducing any sort of code redundancy that may occur when writing your TypeScript code! I won’t be going through every single one, but if you would like to see examples of all of them, check out the documentation site here!
Partial<T> and Required<T>
TypeScript provides two utility types, Partial<T> and Required<T>. These are types that when you supply T as a type, will make all the properties optional or required, respectively. If you are wanting to only accept every single property even if some are listed required in the original type, or if you are fine with partial amounts of values from the field, you can do that! You would want to use Partial<T> whenever you need to build up an object in “stages”, and Required<T> when you want to ensure that every field is present.
Here is an example of Partial<T>:
interface Cat {
color: string;
breed: string;
}
// Since all the properties in type Cat are required, we need them all!
const blackTabby: Cat = {
color: 'black',
breed: 'tabby',
};
// Partial<Cat> means that both `color` and `breed` will become optional!
const orangeCat: Partial<Cat> = {
color: 'orange',
};
Here is an example of Required<T>:
interface User {
uuid: string;
name?: string;
email?: string;
}
const basicUser: User = {
// Here our interface only required uuid, so this is satisfied.
uuid: '123',
};
const fullUser: Required<User> = {
// Since we used Required here, we need all fields!
uuid: '456',
name: 'Joe Shmoe',
email: 'Joe.Shmoe@email.com',
};
Readonly<T>
The Readonly<T> type that will make the properties set to readonly preventing reassignment! This is useful for when you want to guarantee certain values like a UUID from a type so it can’t get accidentally overridden in the codebase. Similar with other utility types, you pass the type parameter T, and it will convert every property into readonly.
type UserId = string;
interface User {
uuid: Readonly<UserId>; // Immutable!
name: string;
}
const user: User = {
uuid: '123',
name: 'Joe Shmoe',
};
user.uuid = '456'; // ❌ Error: cannot assign to 'uuid' because it is a read-only property
user.name = 'Shmoe Joe'; // ✅ OK: 'name' is still writable```
Record<K, T>
The Record<K ,T> type constructs an object type that contains the K keys and whos values are T type. You would want to use this whenever you have a known set of keys and want to enforce that all keys are present. This also lets you constrain what the object keys and values should be depending on what you pass into the record. Every key in K must be present when using Record<K, T>!
// EnemyInfo will only allow string keys, and boolean values!
type EnemyInfo = Record<string, boolean>;
const enemyInfo: EnemyInfo = {
isAlive: true,
isAttacking: false,
};
// Only these three enemy names are alloawed as keys.
type EnemyNames = 'slime' | 'goblin' | 'dragon';
const enemies: Record<EnemyNames, EnemyInfo> = {
slime: { isAlive: true, isAttacking: false },
goblin: enemyInfo,
dragon: { isAlive: false, isAttacking: false },
};
Pick<T, K> and Omit<T, K>
The Pick<T, K> type will construct an object type based on T and only picking the types passed into K. This way, you can selectively choose (or “pick”) what properties you want from this object. Very helpful if you only need specific properties in a constructed type! Omit<T, K> type will construct an object type based on T and omitting the types passed into K. This is the exact opposite of Pick<T, K>! This will allow you to remove object properties that you want to exclude.
Here is an example of Pick<T, K>
type Enemy = {
attackDamage: number;
hp: number;
name: string;
specialAttack?: () => void;
};
// This will give you a new type with only hp and name in the properties!
type BasicEnemy = Pick<Enemy, 'hp' | 'name'>;
const basicEnemy: BasicEnemy = {
hp: 10,
name: 'attack_dummy',
};
Here is an example of Omit<T, K>
type PlayerData = {
id: string;
score: number;
createdAt: Date;
updatedAt: Date;
name: string;
};
// This will give you a new type with only id, score, and name!
type PlayerPayload = Omit<PlayerData, 'createdAt' | 'updatedAt'>;
const playerPayload: PlayerPayload = {
id: '123',
score: 123,
name: 'Joe_Shmoe',
};
These are just a few examples of Utility types that TypeScript gives you by default. You may not need to use all of these in your day to day, but can be very helpful in certain situations! I personally run into all of these in a day by day basis, and find it incredibly useful than having to manually create new types with the values I may or may not want. For more information, make sure to check out the official documentation here!