TypeScript provides several utility types that help in common type transformations. These built-in utility types save time and improve code quality by enforcing consistent transformations of your existing types.
Utility types are pre-defined generic types that operate on other types to produce new variations. They allow you to modify types in a standardized way without duplicating type definitions, ensuring your codebase remains DRY (Don't Repeat Yourself).
The Partial utility type constructs a type with all properties of the input type set to optional.
Partial utility type
interfaceUser { id:number; name:string; email:string; age:number;}// All properties are now optionaltypePartialUser=Partial<User>;// This is valid - we can provide just some propertiesconstuserUpdate:PartialUser= { name:"Alice Smith", email:"alice.smith@example.com",};functionupdateUser(id:number, userData:Partial<User>) {// Update only the provided fields// ...}
This is particularly useful for update operations where only a subset of fields may be modified.
Required<Type>
The Required utility type constructs a type with all properties of the input type set to required, the opposite of Partial.
Required utility type
interfaceConfig { endpoint?:string; timeout?:number; retries?:number;}// All properties are now requiredtypeRequiredConfig=Required<Config>;// Error! Missing properties// const config: RequiredConfig = { endpoint: "api/users" };// ValidconstcompleteConfig:RequiredConfig= { endpoint:"api/users", timeout:5000, retries:3,};
This helps ensure all properties are provided when needed.
Readonly<Type>
The Readonly utility type constructs a type with all properties set to read-only.
Readonly utility type
interfaceTodo { title:string; description:string; completed:boolean;}// All properties are now readonlytypeReadonlyTodo=Readonly<Todo>;consttodo:ReadonlyTodo= { title:"Learn TypeScript", description:"Study utility types", completed:false,};// Error! Cannot modify readonly property// todo.completed = true;
This is useful for creating immutable objects or ensuring values aren't accidentally modified.
Record<Keys, Type>
The Record utility type constructs an object type with a specific set of keys and a defined type for values.
Record utility type
typeUserRole="admin"|"user"|"guest";// Creates an object with keys from UserRole and values of stringtypeRoleDescription=Record<UserRole,string>;constroleDescriptions:RoleDescription= { admin:"Full access to all resources", user:"Access to own resources", guest:"Limited read-only access",};// Strongly typed object with specific keystypeAPIEndpoints=Record<string,string>;constendpoints:APIEndpoints= { users:"/api/users", posts:"/api/posts", comments:"/api/comments",};
Record is excellent for creating dictionaries, lookup tables, or mapping types.
Pick<Type, Keys>
The Pick utility type constructs a type by picking a set of properties from another type.
Pick utility type
interfaceArticle { id:number; title:string; content:string; author:string; createdAt:Date; updatedAt:Date; tags:string[]; published:boolean;}// Only include id, title, and authortypeArticlePreview=Pick<Article,"id"|"title"|"author">;constpreview:ArticlePreview= { id:1, title:"Understanding TypeScript Utility Types", author:"Jane Doe",// Other fields are not allowed};
This helps create more specific subtypes focused only on the properties you need.
Omit<Type, Keys>
The Omit utility type constructs a type by omitting a set of properties from another type. It's the opposite of Pick.
This is particularly useful for removing sensitive or irrelevant properties from types.
Exclude<Type, ExcludedUnion>
The Exclude utility type constructs a type by excluding all union members that are assignable to ExcludedUnion.
Exclude utility type
typeAllEventTypes="click"|"hover"|"focus"|"blur"|"input"|"change";// Create a type without mouse eventstypeNonMouseEvents=Exclude<AllEventTypes,"click"|"hover">;// Result: "focus" | "blur" | "input" | "change"functionaddNonMouseEventListener(event:NonMouseEvents,handler: () =>void) {// ...}// ValidaddNonMouseEventListener("focus", () =>console.log("Element focused"));// Error! 'click' is not assignable to parameter of type NonMouseEvents// addNonMouseEventListener("click", () => console.log("Element clicked"));
This is helpful for filtering union types to exclude specific values.
Extract<Type, Union>
The Extract utility type constructs a type by extracting all union members from Type that are assignable to Union.
Extract utility type
typeFormElements="input"|"select"|"textarea"|"button"|"label";typeInteractiveElements="button"|"input"|"a"|"select"|"textarea";// Extract common elements between the two typestypeInteractiveFormElements=Extract<FormElements,InteractiveElements>;// Result: "input" | "select" | "textarea" | "button"functionsetupFormValidation(element:InteractiveFormElements) {// ...}
Extract is useful for finding the common subset between two union types.
NonNullable<Type>
The NonNullable utility type constructs a type by excluding null and undefined from Type.
NonNullable utility type
typeNullable=string|null|undefined;// Remove null and undefinedtypeNonNullableString=NonNullable<Nullable>;// Result: stringfunctionprocessValue(value:NonNullableString) {// No need to check for null or undefinedreturnvalue.toUpperCase();}
This is helpful when you need to ensure values are neither null nor undefined.
Parameters<Type>
The Parameters utility type extracts the parameter types of a function type as a tuple.
Parameters utility type
functionfetchUser(id:number, includeDetails:boolean=false):Promise<User> {// ...returnPromise.resolve({} asUser);}// Extract parameter types from functiontypeFetchUserParams=Parameters<typeof fetchUser>;// Result: [id: number, includeDetails?: boolean | undefined]// Use it for function composition or mockingfunctionpreprocessFetchUserParams(...args:FetchUserParams) {const [id,includeDetails] = args;console.log(`Preparing to fetch user ${id} with details: ${includeDetails}`);return args;}constresult=preprocessFetchUserParams(42,true);
This is useful for working with function types without repeating parameter definitions.
ReturnType<Type>
The ReturnType utility type extracts the return type of a function type.
ReturnType utility type
functioncreateUser(name:string, email:string) {return { id:Date.now(), name, email, createdAt:newDate(), };}// Extract return type from functiontypeUser=ReturnType<typeof createUser>;// We can use the extracted typefunctionupdateUserCache(user:User) {// ...}constnewUser=createUser("Alice","alice@example.com");updateUserCache(newUser); // Type-safe
This allows you to reuse the return type of a function without explicitly defining it.
InstanceType<Type>
The InstanceType utility type extracts the instance type of a constructor function type.
InstanceType utility type
classApiClient { baseUrl:string;constructor(baseUrl:string) {this.baseUrl = baseUrl; }asyncget(endpoint:string) {// ... }}// Extract the instance type from the classtypeApiClientInstance=InstanceType<typeof ApiClient>;functionconfigureApiClient(client:ApiClientInstance) {// ...}constclient=newApiClient("https://api.example.com");configureApiClient(client); // Type-safe
This is helpful when working with classes and their instance types.
The Awaited utility type models the await operator in async functions, extracting the type from a Promise.
Awaited utility type
asyncfunctionfetchUserData(id:number) {// ...return { id, name:"John", email:"john@example.com" };}// Extracts the resolved type from the PromisetypeUserData=Awaited<ReturnType<typeof fetchUserData>>;// Equivalent to: type UserData = { id: number; name: string; email: string; }functionprocessUserData(userData:UserData) {console.log(`Processing user: ${userData.name}`);}// Usage in an async contextasyncfunctionmain() {constdata=awaitfetchUserData(123);processUserData(data); // Type-safe}
This is particularly useful when working with async functions and Promises.
ThisParameterType<Type>
The ThisParameterType utility type extracts the type of the this parameter from a function type.
Utility types can be combined to create more complex type transformations.
Combining utility types
interfaceUser { id:string; name:string; email:string; password:string; settings: { theme:string; notifications:boolean; }; roles:string[]; createdAt:Date;}// Create a type for user creation (no id or createdAt)typeCreateUserData=Omit<User,"id"|"createdAt">;// Create a type for public user data (no password)typePublicUserData=Omit<User,"password">;// Create a type for updating user settings onlytypeUserSettingsUpdate=Partial<User["settings"]>;// Create a type for a user with specific rolestypeAdminUser=User& { roles: ["admin",...string[]] };// Create a type for a minimal user displaytypeUserSummary=Pick<User,"id"|"name"> & { joined:string };functionformatUser(user:User):UserSummary {return { id:user.id, name:user.name, joined:user.createdAt.toLocaleDateString(), };}
// Define base entity typeinterfaceEntity { id:string; createdAt:string; updatedAt:string;}// Define specific entity typesinterfaceUserextendsEntity { name:string; email:string; isActive:boolean;}interfacePostextendsEntity { title:string; content:string; authorId:string; isPublished:boolean;}// API response wrapper typeinterfaceApiResponse<T> { data:T; meta: { status:number; message:string; };}// Create types for different API operationstypeCreateUserRequest=Omit<User,keyofEntity|"isActive">;typeUpdateUserRequest=Partial<Omit<User,keyofEntity>>;typeUserResponse=ApiResponse<User>;// Function to create a userasyncfunctioncreateUser(userData:CreateUserRequest):Promise<UserResponse> {constresponse=awaitfetch("/api/users", { method:"POST", headers: { "Content-Type":"application/json" }, body:JSON.stringify(userData), });returnresponse.json();}
Form State Management
Form state management
interfaceFormField<T> { value:T; touched:boolean; error?:string; required:boolean;}// Create form state type from a data modeltypeFormState<T> = { [KinkeyofT]:FormField<T[K]>;};// Create a validator results typetypeValidationResults<T> = { [KinkeyofT]?:string;};interfaceUserData { username:string; email:string; password:string; age:number;}// Create a type for the user form statetypeUserFormState=FormState<UserData>;// Initialize form statefunctioninitializeForm<T>(initialData:Partial<T>, requiredFields:Array<keyofT>):FormState<T> {// Implementation omitted for brevityreturn {} asFormState<T>;}// Validate form with type-safe validatorstypeValidatorFn<T> = (value:T) =>string|undefined;typeValidators<T> = { [KinkeyofT]:ValidatorFn<T[K]>;};// Usage exampleconstvalidators:Validators<UserData> = {username: (value) => (value.length<3?"Username must be at least 3 characters":undefined),email: (value) => (!value.includes("@") ?"Email must be valid":undefined),password: (value) => (value.length<8?"Password must be at least 8 characters":undefined),age: (value) => (value <18?"You must be at least 18 years old":undefined),};
Choose the right utility type for the task: Understand what each utility type does and select the one that best fits your needs.
Choosing the right utility
// DON'T: Creating a new type that duplicates PartialtypeOptionalUser= { id?:number; name?:string; email?:string;};// DO: Use the built-in Partial utilitytypeOptionalUser=Partial<User>;
Compose utility types for complex transformations: Combine multiple utility types for more specific type transformations.
Composing utility types
// Create a type for updating a user without changing sensitive fieldstypeSafeUserUpdate=Partial<Omit<User,"id"|"role"|"password">>;// Create a read-only view of specific user fieldstypeUserSummary=Readonly<Pick<User,"id"|"name"|"email">>;
Create custom utility types for repeated patterns: If you find yourself performing the same type transformations repeatedly, create a custom utility type.
Custom utility types
// Custom utility type for API request parameterstypeRequestParams<T> = { [key:string]:string|number|boolean; include?:Array<keyofT>; fields?:Array<keyofT>;};// UsagefunctionfetchUsers(params:RequestParams<User>) {// ...}
Document complex utility types: When combining or creating utility types, add comments to explain what they represent.
Documenting utility types
/** * Represents a user record with only public fields, * made read-only to prevent accidental modification. */typePublicUserProfile=Readonly<Omit<User,"password"|"email">>;/** * Represents an API payload for updating partial user data, * excluding system-managed fields. */typeUserUpdatePayload=Partial<Omit<User,"id"|"createdAt"|"updatedAt">>;
Use mapped types for more dynamic transformations: For complex transformations, combine utility types with mapped type modifiers.
Advanced mapped types
// Make all string properties optional and all number properties readonlytypeSpecialTransform<T> = {readonly [KinkeyofTasT[K] extendsnumber?K:never]:T[K];} & { [KinkeyofTasT[K] extendsstring?K:never]?:T[K];};interfaceProduct { id:number; sku:number; name:string; description:string; price:number; category:string;}// Result: id, sku, price are readonly; name, description, category are optionaltypeTransformedProduct=SpecialTransform<Product>;
Custom Utility Types: Create your own utility types for domain-specific transformations
Using utility types effectively makes your TypeScript code more maintainable, type-safe, and DRY. Remember to avoid any type and use unknown with proper type guards when dealing with data of uncertain types. When possible, combine multiple utility types to create precise type definitions that match your exact needs.
For complex data transformations, especially when working with external APIs or form input validation, utility types provide standardized patterns that ensure strong typing throughout your application.