We use cookies to enhance your experience on the site
CodeWorlds

TypeScript Utility Types

Dr. Henry Wu reviews the genetic database of Jurassic Park. "We have a Dinosaur interface with 15 properties," he says, "but for the update form I need only 5, and for the preview - just 3. Do I have to create a separate interface for each case?" Fortunately, TypeScript offers Utility Types - ready-made transformations that create new types based on existing ones.

What are utility types?

Utility Types are generic types built into TypeScript that allow you to transform existing types into new ones. Instead of writing separate interfaces for each variant, you can use one base type and create derived types from it.

Partial<T> - all properties optional

Partial<T>
makes all properties of type
T
optional. Ideal for updates where we don't want to require all fields to be provided:

1interface Dinosaur {
2  name: string;
3  species: string;
4  dangerLevel: number;
5  enclosureId: string;
6  dietType: 'herbivore' | 'carnivore' | 'omnivore';
7}
8
9// Partial<Dinosaur> = all fields optional
10type DinosaurUpdate = Partial<Dinosaur>;
11
12// Now we can pass only the changed fields
13function updateDinosaur(id: string, changes: DinosaurUpdate): void {
14  console.log(`Updating dinosaur ${id}:`, changes);
15}
16
17// Correct - we pass only what we're changing
18updateDinosaur('DINO-001', { dangerLevel: 9 });
19updateDinosaur('DINO-002', { name: 'Blue', enclosureId: 'E-7' });

Required<T> - all properties required

Required<T>
is the opposite of
Partial
- it makes all properties (even optional ones) required:

1interface DinosaurConfig {
2  name: string;
3  trackingChip?: boolean;  // optional
4  feedingSchedule?: string; // optional
5}
6
7// Required<DinosaurConfig> = everything required
8type CompleteDinosaurConfig = Required<DinosaurConfig>;
9
10// Now trackingChip and feedingSchedule are also required
11const config: CompleteDinosaurConfig = {
12  name: 'Rex',
13  trackingChip: true,        // must be provided
14  feedingSchedule: '08:00'   // must be provided
15};

Pick<T, K> - select properties

Pick<T, K>
creates a new type containing only selected properties from
T
:

1// Only name and species from the full Dinosaur interface
2type DinosaurPreview = Pick<Dinosaur, 'name' | 'species'>;
3
4// DinosaurPreview = { name: string; species: string; }
5
6const preview: DinosaurPreview = {
7  name: 'Blue',
8  species: 'Velociraptor'
9};
10
11// Useful e.g. for a list displayed on screen
12function renderDinosaurList(dinosaurs: DinosaurPreview[]): void {
13  dinosaurs.forEach(dino => {
14    console.log(`${dino.name} (${dino.species})`);
15  });
16}

Omit<T, K> - omit properties

Omit<T, K>
is the opposite of
Pick
- it creates a new type without selected properties:

1// Dinosaur without enclosureId (e.g. for a newly discovered species)
2type DinosaurWithoutEnclosure = Omit<Dinosaur, 'enclosureId'>;
3
4const newDiscovery: DinosaurWithoutEnclosure = {
5  name: 'New species',
6  species: 'Unknownus rex',
7  dangerLevel: 5,
8  dietType: 'carnivore'
9  // we don't need to provide enclosureId
10};
11
12// Omit multiple fields at once
13type DinosaurBasicInfo = Omit<Dinosaur, 'enclosureId' | 'dangerLevel'>;

Record<K, V> - typed dictionary

Record<K, V>
creates an object type where keys have type
K
and values have type
V
:

1// Dictionary: zone name -> list of dinosaurs
2type ZoneMap = Record<string, Dinosaur[]>;
3
4const parkZones: ZoneMap = {
5  'Zone-A': [{ name: 'Rex', species: 'T-Rex', dangerLevel: 10, enclosureId: 'E-1', dietType: 'carnivore' }],
6  'Zone-B': [{ name: 'Trike', species: 'Triceratops', dangerLevel: 3, enclosureId: 'E-2', dietType: 'herbivore' }]
7};
8
9// Record with limited keys (union type)
10type DietCount = Record<'herbivore' | 'carnivore' | 'omnivore', number>;
11
12const census: DietCount = {
13  herbivore: 45,
14  carnivore: 12,
15  omnivore: 8
16};

Readonly<T> - immutable type

Readonly<T>
makes all properties read-only:

1type FrozenDinosaur = Readonly<Dinosaur>;
2
3const frozenDNA: FrozenDinosaur = {
4  name: 'Amber Specimen',
5  species: 'Unknown',
6  dangerLevel: 0,
7  enclosureId: 'LAB-1',
8  dietType: 'herbivore'
9};
10
11// frozenDNA.name = 'Changed'; // Error! Cannot assign to 'name' because it is a read-only property

Combining utility types

The real power comes from combining several utility types:

1// Dinosaur creation form: all fields required EXCEPT enclosureId
2type CreateDinosaurForm = Omit<Dinosaur, 'enclosureId'> & {
3  enclosureId?: string; // optional when creating
4};
5
6// Edit form: only selected fields, all optional
7type EditDinosaurForm = Partial<Pick<Dinosaur, 'name' | 'dangerLevel' | 'enclosureId'>>;
8
9// Immutable preview with selected fields
10type DinosaurReadonlyPreview = Readonly<Pick<Dinosaur, 'name' | 'species' | 'dangerLevel'>>;
11
12const readonlyPreview: DinosaurReadonlyPreview = {
13  name: 'Blue',
14  species: 'Velociraptor',
15  dangerLevel: 8
16};
17// readonlyPreview.dangerLevel = 10; // Error!

Summary

TypeScript Utility Types allow creating variants of existing types without code duplication:

| Type | Action | |------|--------| |

Partial<T>
| All fields optional | |
Required<T>
| All fields required | |
Pick<T, K>
| Select specific fields | |
Omit<T, K>
| Omit specific fields | |
Record<K, V>
| Typed dictionary | |
Readonly<T>
| All fields read-only |

As Dr. Wu says: "Don't create 10 separate interfaces when one base type and utility types will handle it elegantly and safely!"

Go to CodeWorlds