NashTech Blog

Top 5 TypeScript Utility Types Every Frontend Dev Should Master (with real-world snippets)

Table of Contents

Utility types are TypeScript’s “power tools.” They let you reshape existing types without repeating yourself—perfect for React props, API payloads, and state models. Here are the 5 I reach for daily, with practical examples you can paste into a project.

1) Partial<T> — make everything optional

Great for form drafts, step-by-step wizards, or PATCH requests where only some fields are sent.

type User = {
  id: string;
  name: string;
  email: string;
  avatarUrl?: string;
};

// Form state: user can fill this gradually
type UserDraft = Partial<User>;

const initialDraft: UserDraft = {}; // valid
const patchUser = (id: string, payload: Partial<User>) => {
  return fetch(`/api/users/${id}`, {
    method: "PATCH",
    body: JSON.stringify(payload),
  });
};

patchUser("123", { name: "Ada" }); // only send what changed

Gotcha: Partial is shallow—nested objects remain required inside their own shape. For deep partials, define a DeepPartial helper.

2) Pick<T, K> — choose a subset

Use when you only need a slice of a large type, e.g., rendering compact UI cards or passing trimmed props down a component tree.

type Product = {
  id: string;
  name: string;
  price: number;
  description: string;
  inventory: number;
};

type ProductCardData = Pick<Product, "id" | "name" | "price">;

function ProductCard(props: { data: ProductCardData }) {
  return (
    <div>
      <h3>{props.data.name}</h3>
      <p>${props.data.price}</p>
    </div>
  );
}

Tip: Pick is safer than redefining a small type—if the original field changes, your Pick stays in sync.

3) Omit<T, K> — everything except…

Perfect when a base type is almost right, but you must remove one or two fields (e.g., prevent id from being set by form input).

type NewProductInput = Omit<Product, "id" | "inventory">;

async function createProduct(payload: NewProductInput) {
  // backend generates id; inventory comes from a different system
  await fetch("/api/products", {
    method: "POST",
    body: JSON.stringify(payload),
  });
}

createProduct({
  name: "Desk Lamp",
  price: 49.99,
  description: "Minimalist lamp",
  // id: "nope", // type error
  // inventory: 100, // type error
});

Rule of thumb: If you’re removing fields from a known shape, prefer Omit; if you’re selecting a few fields, prefer Pick.

4) Record<K, T> — map keys to a consistent type

Ideal for lookup tables, feature flags, route-to-component maps, or i18n dictionaries.

type FeatureKey = "betaBanner" | "newCheckout" | "perfTracing";

const features: Record<FeatureKey, boolean> = {
  betaBanner: true,
  newCheckout: false,
  perfTracing: true,
};

// Route registry
type Route = "/home" | "/settings" | "/profile";

type RouteConfig = {
  title: string;
  requiresAuth: boolean;
};

const routes: Record<Route, RouteConfig> = {
  "/home": { title: "Home", requiresAuth: false },
  "/settings": { title: "Settings", requiresAuth: true },
  "/profile": { title: "Profile", requiresAuth: true },
};

Power move: Combine as const unions for keys with Record to keep everything type-safe and auto-complete friendly.

5) ReturnType<T> — infer a function’s output

Great for deriving types from factories, selectors, Redux actions/thunks, or service functions—no need to duplicate shapes.

// Service layer
function makeUser(name: string) {
  return {
    id: crypto.randomUUID(),
    name,
    createdAt: new Date(),
  };
}

// Reuse the exact return type everywhere:
type UserModel = ReturnType<typeof makeUser>;

const ada: UserModel = makeUser("Ada");

// React selector example
const selectUserFullName = (u: { first: string; last: string }) =>
  `${u.first} ${u.last}`;

type FullName = ReturnType<typeof selectUserFullName>; // string

Companion: Parameters<T> extracts a function’s parameter tuple—handy for higher-order functions.

type MakeUserArgs = Parameters<typeof makeUser>; // [name: string]

Bonus combos you’ll actually use

Partial + Omit (safe form inputs)

type UserEditable = Partial<Omit<User, "id">>;
// id cannot be edited; everything else is optional

Record + ReturnType (registry of factories)

const factories = {
  user: (name: string) => ({ id: "u1", name }),
  product: (name: string) => ({ sku: "p1", name }),
};

type Factories = typeof factories;
type FactoryOutputs = { [K in keyof Factories]: ReturnType<Factories[K]> };
// {
//   user: { id: string; name: string };
//   product: { sku: string; name: string };
// }

Pick + Readonly (immutable view models)

type ImmutableProductCard = Readonly<Pick<Product, "id" | "name" | "price">>;
// props now cannot be mutated inside the component

When to use what (quick reference)

  • Partial<T>: Draft/patch shapes where fields may arrive later.
  • Pick<T, K>: You only need a small subset (UI cards, compact props).
  • Omit<T, K>: Base type is right—remove unsafe/irrelevant fields (e.g., id).
  • Record<K, T>: Keyed registries and lookups (routes, features, i18n).
  • ReturnType<T>: Reuse exact outputs from factories/selectors without duplication.

Summarize

Learn these five and you’ll eliminate tons of duplicate types, keep UI and API contracts in lockstep, and move faster with fewer runtime surprises. Bookmark this and refactor a couple of spots in your codebase—you’ll feel the difference immediately.

Picture of tuanmaic

tuanmaic

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top