Building Your Own Utilities

Advanced · 20 min read · ▶ live playground · ✦ checkpoint

Most utility types should be boring, small, and named after the job they do. TypeScript gives you a large built-in toolkit; your team should add a custom utility only when it makes a repeated idea easier to read and harder to misuse.

Sections 10 and 11 gave you the mechanics. Lessons 12.1 through 12.3 gave you the built-ins. This final lesson is about the judgment call: when a tiny team utility earns a name, how to document it, and where to stop.

Name The Job

A custom utility type is part of your team's vocabulary. Treat it like a function API: choose a clear name, keep the implementation short, and add a comment when the name alone is not enough.

Bad utility names describe tricks: MappedIntersectionCleaner, CrazyDeepThing, TypeMagic. Good utility names describe outcomes: Prettify, DeepReadonly, Entries, ValueOf.

Here is the most common tiny helper:

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

Prettify<T> rebuilds an object type key by key. It does not make the type safer or looser. Its job is display: intersections such as Pick<Account, "id" | "email"> & { displayName: string } often produce noisy hovers, while the rebuilt type usually appears as one plain object shape.

Document helpers at the definition site, not in every use:

/** Rebuilds an object type so editor hovers show the final shape clearly. */
type Prettify<T> = { [K in keyof T]: T[K] } & {};

That comment is short because the utility is short. If the comment needs a paragraph, the utility probably needs a smaller scope or a better name.

DeepReadonly, Shallow Example

Readonly<T> from 12.1 only marks the top-level properties as read-only. Some teams also use a DeepReadonly<T> helper for nested config objects.

Keep the first version plain and honest. This teaching version is for ordinary nested object data, not every JavaScript value in existence:

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

This example is intentionally shallow. Section 11.4 already taught the cost of recursive types. A production-grade deep utility has to think about arrays, tuples, functions, dates, maps, and checker performance. Your team helper should say what it supports. A comment like "Plain JSON-like config objects only" is better than pretending one alias models all JavaScript values perfectly.

Entries And ValueOf

ValueOf<T> reads the union of all property value types. Entries<T> turns each property into a key/value tuple and unions those tuples.

These are small wrappers around keyof and indexed access, but the names are useful because many teams talk about "values of this object shape" and "typed entries" constantly.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

ValueOf<FeatureDefaults> is boolean | number | "simple" | "advanced". Entries<FeatureDefaults>[number] is a union of three tuples: ["quickSearch", boolean], ["maxSeats", number], and ["mode", "simple" | "advanced"].

One important boundary: this type does not magically change Object.entries into a perfectly typed runtime function. Object.entries is a broad JavaScript API. If you write a runtime helper around it, you must decide how much checking, assertion, or validation belongs there. This lesson is about naming static shapes you already trust.

Study Mature Utility Design

When you want examples of mature utility naming, study type-fest. Do not copy a giant utility just because it looks impressive. Read it for design habits:

  • Names describe outcomes a caller wants.
  • Utilities are documented near their definitions.
  • Many helpers are small compositions of primitives you already know.
  • Edge cases are part of the design, not afterthoughts hidden in one huge alias.

That is the lesson to steal. A team utility file should feel like a shelf of well-labeled tools, not a puzzle box.

Before adding a utility, ask four questions:

  • Is there a built-in utility that already says this?
  • Will this name make five call sites easier to read?
  • Can the implementation fit on a few lines?
  • Can we state its supported inputs clearly?

If the answer is no, write the explicit type at the call site. Repetition is sometimes cheaper than abstraction, especially in the type layer.

This closes the utility toolkit section: built-ins first, custom helpers only when they improve communication. Next section shifts to runtime safety. Utility types reshape trusted static types, but data from APIs, storage, user input, or environment variables still needs runtime validation before those types are honest.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion