DEV Community

Cover image for TypeScript Tricks I Actually Use Day to Day
Abdulmalik Muhammad
Abdulmalik Muhammad

Posted on

TypeScript Tricks I Actually Use Day to Day

I've been writing TypeScript for a few years now across React Native, Node.js, and a bunch of different product types. And there's a gap between what the docs teach you and what you actually end up reaching for every day.
Here are the patterns I keep coming back to.

Discriminated unions for state management
This one changed how I model data. Instead of a bunch of optional fields that may or may not exist, you define each state explicitly.

type RequestState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: User }
  | { status: "error"; message: string };
Enter fullscreen mode Exit fullscreen mode

Now TypeScript knows exactly what's available in each case. No more data might be undefined checks scattered everywhere.

satisfies instead of direct type annotation

This one's newer but I use it a lot now. The difference is subtle but useful.

const config = {
  theme: "dark",
  language: "en",
} satisfies Record<string, string>;
Enter fullscreen mode Exit fullscreen mode

With satisfies, you get type checking without losing the literal types. If you annotate directly with Record, you lose the specific values. Small thing, big difference when you're chaining or inferring from it.


Utility types you actually need

Everyone knows Partial and Required. These are the ones I reach for more often:

// Pick only what you need
type Preview = Pick<Article, "title" | "slug" | "createdAt">;

// Exclude what you don't
type PublicUser = Omit<User, "password" | "token">;

// Make specific fields optional
type UpdateInput = Partial<Pick<User, "name" | "bio">> & Pick<User, "id">;
Enter fullscreen mode Exit fullscreen mode

That last one is something I use constantly for update/patch endpoints.


Template literal types for string contracts
Useful when you're dealing with event names, routes, or anything string-based that needs structure.

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/${string}`;
type Route = `${HttpMethod} ${Endpoint}`;

const route: Route = "GET /users"; // valid
const bad: Route = "FETCH /users"; // error
Enter fullscreen mode Exit fullscreen mode

Catches a whole class of bugs at compile time instead of runtime.


infer for extracting types from generics

Looks scary at first but once it clicks, you use it everywhere.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
Enter fullscreen mode Exit fullscreen mode

I use UnpackPromise constantly when working with async functions and I need the resolved type without awaiting.


Const assertions for config objects

Stop widening your types when you don't need to.

const ROLES = ["admin", "user", "guest"] as const;
type Role = typeof ROLES[number]; // "admin" | "user" | "guest"
Enter fullscreen mode Exit fullscreen mode

Clean, no manual duplication, and the type stays in sync automatically.

None of these are exotic. They're just the things that come up over and over when you're building real products. The more you use them the more the TypeScript compiler starts feeling like a teammate rather than something you're fighting against.

That's the shift worth chasing.

Top comments (0)