DEV Community

Cover image for đź’ˇ Utility Types That Supercharge Developer Experience in TypeScript
Vansh Chaurasiya
Vansh Chaurasiya

Posted on

đź’ˇ Utility Types That Supercharge Developer Experience in TypeScript

đź’ˇ Utility Types That Supercharge Developer Experience in TypeScript

If you’ve spent any time working with TypeScript, you know the language is a gift that keeps on giving — especially when it comes to types that make your life easier.

But even with the rich standard library, you’ll often find yourself rewriting certain utility types again and again — making something nullable, prettifying an inferred type, or toggling optional keys.

Today, I’ll share a small but powerful set of TypeScript helper types that can drastically boost your developer experience (DX) and simplify your type logic.

These utilities are small, elegant, and built to solve real problems you hit every day in complex projects.


⚙️ The Utility Pack

Here’s the full collection of the helper types we’ll explore today:

/** Union of all JavaScript primitive types (excluding `symbol` and `function`) */
type Primitive = string | number | bigint | boolean | null | undefined;

/** Wraps a type in `null` */
type Nullable<T> = T | null;

/** Deeply nullable (all nested props can be null) */
type NullableDeep<T> = {
  [K in keyof T]: T[K] extends object ? NullableDeep<T[K]> | null : T[K] | null;
};

/** String literal representation of a primitive */
type Enum<T extends Primitive> = `${T}`;

/** Flattens and normalizes a type’s shape for readability */
type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};

/** Makes only specific keys optional */
type Partialize<T, K extends keyof T> = Prettify<
  Omit<T, K> & Partial<Pick<T, K>>
>;

/** Makes only specific keys required */
type RequireBy<T, K extends keyof T> = Prettify<
  Omit<T, K> & Required<Pick<T, K>>
>;
Enter fullscreen mode Exit fullscreen mode

Get it from my github gist

Let’s break them down one by one 👇


🧩 1. Primitive — The Building Blocks

type Primitive = string | number | bigint | boolean | null | undefined;
Enter fullscreen mode Exit fullscreen mode

This type simply represents all serializable primitive types in JavaScript (excluding symbol and function).

Why is this helpful?
Because when you define utility types or constraints, you often want to limit the scope to only serializable or basic values.
For example:

function logPrimitive<T extends Primitive>(value: T) {
  console.log(value);
}
Enter fullscreen mode Exit fullscreen mode

This ensures you never accidentally pass a complex object or a function — great for cleaner, safer APIs.


🧵 2. Nullable<T> — Making a Type Nullable

type Nullable<T> = T | null;
Enter fullscreen mode Exit fullscreen mode

A simple yet surprisingly powerful helper.

Whenever you want to express that a value might be null, instead of repeatedly writing string | null or User | null, you can just wrap it in Nullable<T>.

Example:

type User = { name: string; email: string };
type NullableUser = Nullable<User>;
// Equivalent to: User | null
Enter fullscreen mode Exit fullscreen mode

It’s small, elegant, and improves code readability — especially in domain models or API responses.


🌊 3. NullableDeep<T> — Make Everything Nullable

type NullableDeep<T> = {
  [K in keyof T]: T[K] extends object ? NullableDeep<T[K]> | null : T[K] | null;
};
Enter fullscreen mode Exit fullscreen mode

Ever had to deal with deeply nested types where every property can be null (like in API responses)?
Instead of making every property manually nullable, use this.

Example:

type User = {
  name: string;
  address: {
    city: string;
    country: string;
  };
};

type NullableUser = NullableDeep<User>;

// Result:
type NullableUser = {
  name: string | null;
  address: {
    city: string | null;
    country: string | null;
  } | null;
};
Enter fullscreen mode Exit fullscreen mode

🎯 Use case:
When working with APIs that may return incomplete or partial data — NullableDeep keeps your types aligned without manually updating every nested key.


⚡ 4. Enum<T> — Boosting DX for Enums

type Enum<T extends Primitive> = `${T}`;
Enter fullscreen mode Exit fullscreen mode

This one’s a DX (developer experience) lifesaver.

Here’s the deal — in TypeScript, when you define an enum, hovering over its property doesn’t always show the string values you actually use in your code.

Example:

enum Gender {
  MALE = 'male',
  FEMALE = 'female'
}

type Person = {
  gender: Gender;
};
Enter fullscreen mode Exit fullscreen mode

If you hover over gender, you’ll see enum Gender — not 'male' | 'female'.
That’s fine for strictness, but not so friendly when you want hover hints or intellisense clarity.

Enter our hero:

type Enum<T extends Primitive> = `${T}`;
Enter fullscreen mode Exit fullscreen mode

Usage:

type Person = {
  gender: Enum<Gender>;
};
Enter fullscreen mode Exit fullscreen mode

Now, hover over gender, and you’ll see:

gender: "male" | "female"
Enter fullscreen mode Exit fullscreen mode

✨ Why it matters:

  • Improves hover readability
  • Plays well with JSON APIs and UI forms
  • Great for autocompletion and validation types

🧱 5. Prettify<T> — Making Complex Types Readable

type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};
Enter fullscreen mode Exit fullscreen mode

When you use TypeScript’s utility types like Omit, Pick, or intersections, you often end up with ugly nested inferred types like:

type Example = Omit<User, 'id'> & { id?: string };
Enter fullscreen mode Exit fullscreen mode

Hovering over Example might give you something like:

Omit<User, "id"> & { id?: string; }
Enter fullscreen mode Exit fullscreen mode

Not helpful, right?

By wrapping it in Prettify<T>, TypeScript flattens it for readability:

type PrettyExample = Prettify<Example>;
Enter fullscreen mode Exit fullscreen mode

Now it shows up neatly as:

{ name: string; id?: string; }
Enter fullscreen mode Exit fullscreen mode

🪄 Perfect for hover docs, DX improvements, and cleaner autocomplete.


🧩 6. Partialize<T, K> — Make Specific Keys Optional

type Partialize<T, K extends keyof T> = Prettify<
  Omit<T, K> & Partial<Pick<T, K>>
>;
Enter fullscreen mode Exit fullscreen mode

Sometimes you don’t want to make all properties optional — just a few.

Example:

type User = { name: string; email: string; age: number };
type OptionalEmail = Partialize<User, 'email'>;

// Result:
type OptionalEmail = {
  name: string;
  age: number;
  email?: string;
};
Enter fullscreen mode Exit fullscreen mode

This is especially useful in form states or update payloads where only certain fields are optional.


🔒 7. RequireBy<T, K> — Make Specific Keys Required

type RequireBy<T, K extends keyof T> = Prettify<
  Omit<T, K> & Required<Pick<T, K>>
>;
Enter fullscreen mode Exit fullscreen mode

The opposite of Partialize.
Perfect when you have a type with optional fields, but want to enforce some of them as mandatory in a specific context.

Example:

type User = { name: string; email?: string; age?: number };
type RequireEmail = RequireBy<User, 'email'>;

// Result:
type RequireEmail = {
  name: string;
  email: string;
  age?: number;
};
Enter fullscreen mode Exit fullscreen mode

Ideal for validation layers, admin panels, or API mutations.


đź’¬ Wrapping Up

These types might look simple at first glance, but they can massively improve your TypeScript experience.
They’re reusable, intuitive, and composition-friendly — perfect for any project where type safety and developer ergonomics matter.

Here’s what they bring to the table:

Utility Purpose DX Boost
Primitive Define base JS types Clean constraints
Nullable<T> Make type nullable Simplicity
NullableDeep<T> Deeply nullable object Real-world API resilience
Enum<T> Enum hover clarity Intellisense-friendly
Prettify<T> Flatten nested types Cleaner hovers
Partialize<T, K> Make specific keys optional Form handling
RequireBy<T, K> Make specific keys required Input validation

đź§  Pro Tip:

Keep these in a types/utils.ts file and reuse them across your codebase.
Over time, you’ll notice your type definitions become cleaner, more expressive, and far easier to maintain.


What about you?
Have you built similar DX helpers in your projects?
Drop your favorites — I’d love to discover more community-built utility types! 🚀

Top comments (0)