DEV Community

DatanestDigital
DatanestDigital

Posted on

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

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.

Top comments (0)