Documentation Index Fetch the complete documentation index at: https://mintlify.com/typescript-exercises/typescript-exercises/llms.txt
Use this file to discover all available pages before exploring further.
Exercise 5: Using Partial and Omit
Scenario
You need to filter users by various criteria, but requiring all User properties for the filter would be impractical. You should be able to filter by just age, or just name, or any combination.
The Problem
interface User {
type : 'user' ;
name : string ;
age : number ;
occupation : string ;
}
export function filterUsers ( persons : Person [], criteria : User ) : User [] {
return persons . filter ( isUser ). filter (( user ) => {
const criteriaKeys = Object . keys ( criteria ) as ( keyof User )[];
return criteriaKeys . every (( fieldName ) => {
return user [ fieldName ] === criteria [ fieldName ];
});
});
}
// ❌ Error: Must provide all User properties
filterUsers ( persons , {
age: 23 // Missing name, type, occupation
});
The criteria parameter requires a complete User object, but you only want to filter by specific fields.
Key Concepts
Partial<T> Makes all properties of type T optional
Omit<T, K> Creates a type with all properties of T except those in K
Understanding Utility Types
TypeScript provides built-in utility types for common type transformations:
interface User {
name : string ;
age : number ;
occupation : string ;
}
type PartialUser = Partial < User >;
// Equivalent to:
// {
// name?: string;
// age?: number;
// occupation?: string;
// }
Solution
export function filterUsers ( persons : Person [], criteria : User ) : User [] {
return persons . filter ( isUser ). filter (( user ) => {
const criteriaKeys = Object . keys ( criteria ) as ( keyof User )[];
return criteriaKeys . every (( fieldName ) => {
return user [ fieldName ] === criteria [ fieldName ];
});
});
}
// Must provide all properties
filterUsers ( persons , {
type: 'user' , // Required
name: '' , // Required
age: 23 ,
occupation: '' // Required
});
export function filterUsers (
persons : Person [],
criteria : Partial < Omit < User , 'type' >>
) : User [] {
return persons . filter ( isUser ). filter (( user ) => {
const criteriaKeys = Object . keys ( criteria ) as ( keyof Omit < User , 'type' >)[];
return criteriaKeys . every (( fieldName ) => {
return user [ fieldName ] === criteria [ fieldName ];
});
});
}
// Can filter by any combination of fields
filterUsers ( persons , { age: 23 });
filterUsers ( persons , { name: 'Max' , age: 25 });
filterUsers ( persons , { occupation: 'Astronaut' });
Breaking Down the Solution
Let’s understand Partial<Omit<User, 'type'>> step by step:
Start with User
interface User {
type : 'user' ;
name : string ;
age : number ;
occupation : string ;
}
Apply Omit
Omit < User , 'type' >
// Result:
// {
// name: string;
// age: number;
// occupation: string;
// }
Removes the ‘type’ field since you don’t want to filter by it
Apply Partial
Partial < Omit < User , 'type' >>
// Result:
// {
// name?: string;
// age?: number;
// occupation?: string;
// }
Makes all remaining fields optional
Why exclude 'type' from criteria?
The type field is a discriminant used to identify the object type. When filtering users, you already know they’re users (after persons.filter(isUser)), so filtering by type: 'user' is redundant and could lead to mistakes.
Practical Usage
const persons : Person [] = [
{ type: 'user' , name: 'Max Mustermann' , age: 25 , occupation: 'Chimney sweep' },
{ type: 'user' , name: 'Kate Müller' , age: 23 , occupation: 'Astronaut' },
{ type: 'user' , name: 'Wilson' , age: 23 , occupation: 'Ball' }
];
console . log ( 'Users of age 23:' );
filterUsers ( persons , { age: 23 }). forEach ( logPerson );
// Output:
// - Kate Müller, 23, Astronaut
// - Wilson, 23, Ball
console . log ( 'Astronauts:' );
filterUsers ( persons , { occupation: 'Astronaut' }). forEach ( logPerson );
// Output:
// - Kate Müller, 23, Astronaut
Other Useful Utility Types
Pick<T, K> Select specific properties from a type type UserName = Pick < User , 'name' | 'age' >;
Required<T> Makes all properties required type CompleteUser = Required < PartialUser >;
Readonly<T> Makes all properties read-only type ImmutableUser = Readonly < User >;
Record<K, T> Creates an object type with keys K and values T type UserMap = Record < string , User >;
Combining Utility Types
You can chain utility types for complex transformations:
// Optional fields except 'name'
type PartialExceptName = Partial < User > & Pick < User , 'name' >;
// Required 'name' and 'age', optional others
type RequiredNameAge = Partial < User > & Required < Pick < User , 'name' | 'age' >>;
// Read-only partial user
type ReadonlyPartialUser = Readonly < Partial < User >>;
Utility types compose well together. Think of them as building blocks for creating exactly the type you need.
Type-Safe Object Keys
Notice the type assertion for object keys:
const criteriaKeys = Object . keys ( criteria ) as ( keyof Omit < User , 'type' >)[];
Why is this assertion needed?
Object.keys() returns string[] because JavaScript objects can have string keys added at runtime. TypeScript plays it safe by returning the wider type.When you know the object’s structure (like here with criteria), you can safely assert to the specific keys: // Object.keys returns: string[]
// We assert to: ('name' | 'age' | 'occupation')[]
This allows type-safe property access in the loop.
Common Patterns
Filtering API
Update API
Create API
function filterUsers (
persons : Person [],
criteria : Partial < Omit < User , 'type' >>
) : User [] {
// Implementation
}
What You Learned
Makes all properties optional, useful for filters, updates, and partial data
Removes specific properties from a type, useful for excluding discriminants or auto-generated fields
Utility types can be combined to create precisely the types you need
No need to duplicate type definitions - derive new types from existing ones
Next Steps
Move on to Generics to learn how to create reusable, type-safe functions that work with any type.
Additional Resources