TypeScript Tricks I Actually Use Day to Day

Published: (April 7, 2026 at 07:42 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for 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. 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 };

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 newer feature is handy. The difference is subtle but useful.

const config = {
  theme: "dark",
  language: "en",
} satisfies Record;

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;

// Exclude what you don't
type PublicUser = Omit;

// Make specific fields optional
type UpdateInput = Partial> & Pick;

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 any string‑based 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

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 extends (...args: any[]) => infer R ? R : never;

type UnpackPromise = T extends Promise ? U : T;

I use UnpackPromise constantly when working with async functions and 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"

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.

0 views
Back to Blog

Related posts

Read more »