10 TypeScript Utility Types That Will Clean Up Your Codebase (2026)

javascript dev.to

Most TypeScript codebases are full of types that are copies of other types with one field changed. A User, then a UserUpdate that's the same minus id, then a UserResponse that's the same plus a timestamp. Maintaining those by hand is how types drift out of sync with reality.

TypeScript ships a set of built-in utility types that derive new types from existing ones, so a change in one place propagates everywhere. Here are the ten I reach for constantly, with the real situations they solve.

1. Partial<T> — everything optional

Perfect for update payloads and patch functions where the caller supplies only the fields they're changing.

interface User { id: string; name: string; email: string; }

function updateUser(id: string, changes: Partial<User>) { /* ... */ }
updateUser("1", { email: "new@example.com" });   // ✅ no need to pass name
Enter fullscreen mode Exit fullscreen mode

2. Required<T> — everything mandatory

The inverse. Useful when you receive a loosely-typed config and want to assert that defaults have filled every hole.

interface Options { timeout?: number; retries?: number; }
function run(opts: Required<Options>) { /* every field guaranteed */ }
Enter fullscreen mode Exit fullscreen mode

3. Readonly<T> — freeze the shape at the type level

Signals intent and catches accidental mutation at compile time — great for state objects and function parameters you don't own.

function render(config: Readonly<User>) {
  config.name = "x";   // ❌ Cannot assign to 'name', it is read-only
}
Enter fullscreen mode Exit fullscreen mode

4. Pick<T, K> — keep only the keys you need

Stop hand-copying a subset of fields. Pick derives it.

type UserPreview = Pick<User, "id" | "name">;   // { id: string; name: string }
Enter fullscreen mode Exit fullscreen mode

5. Omit<T, K> — everything except these keys

The one I use most. "A User but without the server-generated id" becomes a one-liner that updates itself when User changes.

type NewUser = Omit<User, "id">;   // { name: string; email: string }
Enter fullscreen mode Exit fullscreen mode

6. Record<K, V> — typed dictionaries

Replaces the vague { [key: string]: V } with something precise, and can constrain the keys to a union.

type Role = "admin" | "editor" | "viewer";
const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};   // miss a role and the compiler tells you
Enter fullscreen mode Exit fullscreen mode

7. ReturnType<T> — infer what a function returns

Invaluable when a function's return type is complex or inferred, and you want a variable typed to match without restating it.

function createStore() { return { count: 0, items: [] as string[] }; }
type Store = ReturnType<typeof createStore>;
Enter fullscreen mode Exit fullscreen mode

8. Parameters<T> — capture an argument list as a tuple

Great for wrappers, decorators, and higher-order functions that must forward arguments faithfully.

type LogArgs = Parameters<typeof updateUser>;   // [string, Partial<User>]
function withLogging(...args: Parameters<typeof updateUser>) {
  console.log("calling updateUser", args);
  return updateUser(...args);
}
Enter fullscreen mode Exit fullscreen mode

9. NonNullable<T> — strip null and undefined

After a guard, narrow a type so the rest of the function doesn't have to keep checking.

type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>;   // User
Enter fullscreen mode Exit fullscreen mode

10. Exclude<T, U> and Extract<T, U> — filter unions

Filter members out of (or into) a union type — the set operations of the type system.

type Status = "active" | "archived" | "deleted";
type Visible = Exclude<Status, "deleted">;     // "active" | "archived"
type Gone    = Extract<Status, "deleted">;     // "deleted"
Enter fullscreen mode Exit fullscreen mode

Compose them for real power

The utilities shine when combined. "A draft is a User without its id, with everything optional" is one expression that tracks User forever:

type UserDraft = Partial<Omit<User, "id">>;

// A response type derived from the model, plus a server field:
type UserResponse = Readonly<User & { createdAt: string }>;
Enter fullscreen mode Exit fullscreen mode

Once your derived types are computed from a single source of truth, an edit to User ripples through UserDraft, UserPreview, and UserResponse automatically — and the compiler flags every place that needs attention.

A working reference beats memorizing

These ten cover the overwhelming majority of day-to-day needs, but the combinations (mapped types, conditional types, template literal types) are where teams reinvent the same helpers. If you'd rather pull from a vetted set, the TypeScript Utility Library collects production-ready type helpers and patterns — deep-partial, strict-omit, branded types, and more — so you stop rewriting them per project.

Takeaway

The fastest way to a cleaner TypeScript codebase isn't more types — it's fewer hand-written ones. Derive types from a single source with Pick, Omit, Partial, and friends, and let the compiler keep them honest as your models evolve.

Source: dev.to

arrow_back Back to Tutorials