TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

TypeScript Utility Types: Practical Examples for Partial, Pick, Omit, Record, and Required

Practical insights, clear explanations, and useful ideas for understanding the web.

October 26, 20266 min read6 topics
TypeScript Utility Types: Practical Examples for Partial, Pick, Omit, Record, and Required
Explore the details
#TypeScript#TypeScript utility types#Partial#Pick and Omit#Record#strict mode
October 26, 20266 min read

TypeScript utility types let you reshape existing types instead of repeating their fields. This guide shows how Partial, Pick, Omit, Record, and Required work in practical user, configuration, and API examples—and where their compile-time transformations stop.

Key Takeaways

  • Partial<T> makes a type’s properties optional; Required<T> makes them required.
  • Pick<T, K> keeps selected properties, while Omit<T, K> removes selected properties from the type.
  • Record<K, V> describes an object whose keys come from K and whose values have type V.
  • These utility types are shallow and do not change or validate JavaScript objects at runtime.
  • Compile examples with strict checking, and add runtime validation when data comes from outside your program.

Start with Partial: user updates and configuration

Partial<T> is useful when an operation accepts only some of an existing object’s properties. A user update, for example, may allow a caller to change a name or email without supplying the user’s ID and role.

interface User {
  id: string;
  displayName: string;
  email: string;
  role: "admin" | "member";
}

type UserPatch = Partial<Pick<User, "displayName" | "email">>;

function updateUser(user: User, patch: UserPatch): User {
  return { ...user, ...patch };
}

const currentUser: User = {
  id: "u-42",
  displayName: "Sam Rivera",
  email: "sam@example.com",
  role: "member",
};

const updatedUser = updateUser(currentUser, { displayName: "Sam R." });

Here, Pick limits the patch to the fields users are allowed to change, and Partial makes those selected fields optional. Choosing the fields explicitly is safer than accepting Partial<User> when callers must not be able to change id or role.

Use Partial for configuration inputs

A configuration function can accept overrides while returning a complete configuration. Consider a nested retry policy:

interface RetryPolicy {
  attempts: number;
  delayMs: number;
}

interface ServiceConfig {
  endpoint: string;
  verbose: boolean;
  retry: RetryPolicy;
}

const defaultConfig: ServiceConfig = {
  endpoint: "https://api.example.com",
  verbose: false,
  retry: { attempts: 3, delayMs: 500 },
};

function configure(input: Partial<ServiceConfig>): ServiceConfig {
  return {
    ...defaultConfig,
    ...input,
    retry: {
      ...defaultConfig.retry,
      ...input.retry,
    },
  };
}

const config = configure({ retry: { attempts: 5, delayMs: 250 } });

Partial<ServiceConfig> changes only the top-level properties. If retry is provided, it must still contain both attempts and delayMs. The explicit nested merge preserves unspecified retry defaults when applying an override.

Use Pick and Omit to describe API shapes

Pick<T, K> describes a type containing only selected properties. It is useful for response shapes that intentionally expose a subset of a domain model.

type UserSummary = Pick<User, "id" | "displayName">;

function getUserSummary(user: User): UserSummary {
  return { id: user.id, displayName: user.displayName };
}

Omit<T, K> describes a type with selected properties excluded. For example, it can describe a public representation without an email address:

type PublicUser = Omit<User, "email">;

function toPublicUser(user: User): PublicUser {
  const { email, ...publicUser } = user;
  return publicUser;
}

The Omit type alone does not remove email from a JavaScript object. The function destructures it out at runtime; the type then describes the returned shape. This distinction matters when preparing API responses or handling sensitive fields: a type annotation is not a data-sanitization step.

Create key-to-value maps with Record

Record<K, V> describes an object with keys of type K and values of type V. When K is a finite union, TypeScript checks that the object supplies the expected keys and that their values match.

type RoleLabels = Record<User["role"], string>;

const roleLabels: RoleLabels = {
  admin: "Administrator",
  member: "Member",
};

function labelRole(role: User["role"]): string {
  return roleLabels[role];
}

If another role is added to User["role"], the roleLabels object will need a corresponding entry. For arbitrary string-keyed dictionaries, a type such as Record<string, boolean> describes the value type associated with string keys; it does not mean every possible string key is already present at runtime.

Use Required for resolved options

An input type often makes fields optional because callers can rely on defaults. After defaults have been applied, Required<T> can describe the complete result.

interface SearchOptions {
  query?: string;
  limit?: number;
}

type ResolvedSearchOptions = Required<SearchOptions>;

function resolveSearchOptions(input: SearchOptions): ResolvedSearchOptions {
  return {
    query: input.query ?? "",
    limit: input.limit ?? 20,
  };
}

const options = resolveSearchOptions({ query: "typescript" });
// options has both query and limit.

Required changes optional property modifiers in the type. It does not fill in missing properties, check values, or make untrusted input safe; the function supplies the defaults here. Validate constraints such as a positive limit separately if the application needs them.

Compile the examples under strict mode

Save the examples in a .ts file, with the interface declarations included, then run the TypeScript compiler in strict mode:

npx tsc --strict --noEmit utility-types.ts

This command assumes Node.js and npm are available and that the TypeScript compiler can be run through npx. For a project that already has TypeScript installed, use its local compiler, such as npx tsc --strict --noEmit. The examples are type-level demonstrations and do not require a framework.

Common Mistakes

  • Assuming these types are deep. Partial<ServiceConfig> makes retry optional, but does not make retry.attempts and retry.delayMs optional. Use a deliberate nested type or merge when nested partial input is required.
  • Treating a type transformation as runtime behavior. Omit does not delete a property, Required does not supply a value, and Record does not create an object. Write code that performs the runtime work.
  • Using Partial<T> for every update request. It may allow changes to fields that the operation should not accept. Select the permitted keys with Pick first.
  • Assuming a type prevents all extra properties. TypeScript checks object literals in many contexts, but its structural type system is not a general runtime filter for additional properties.
  • Forgetting defaults in shallow merges. Spreading a partial top-level configuration over defaults replaces a whole nested object if that nested property is supplied. Merge nested objects separately when preserving their defaults is required.

Best Practices

  • Define a clear base type, then use Pick, Omit, Partial, or Required to express a specific operation’s input or output.
  • Keep update types narrow. Accept only fields the operation is designed to change.
  • Treat values from network requests, files, and other external sources as untrusted. TypeScript utility types do not validate them at runtime.
  • Make nested merge behavior explicit; do not assume a shallow utility type or object spread performs a deep merge.
  • Run tsc --strict regularly so examples and application code are checked with strict type rules.

Conclusion

These five utility types make it easier to express related input, output, and configuration shapes without duplicating type definitions. Use them to clarify what a function accepts or returns, and pair compile-time types with explicit runtime logic wherever data must be transformed or validated.

References