CC0-1.0

typescript-tips-everyone-should-know

A collection of practical TypeScript patterns that improve safety, readability, maintainability, and developer experience. 🗃

A

AllThingsSmitty

Dernière activité 25 sept. 2026
AllThingsSmitty/typescript-tips-everyone-should-know

415

étoiles

5

forks

0

issues ouvertes

awesome-listbest-practicescheatsheetdeveloper-experiencefrontendjavascripttype-safetytypestypescriptweb-development

Ce README est souvent en anglais.

TypeScript Tips Everyone Should Know

Practical TypeScript patterns for writing safer, more maintainable code.

Most of these are small individually. Together, they change how TypeScript code feels day to day.

Table of Contents

  1. Prefer unknown Over any
  2. Let Type Inference Do the Work
  3. Prefer satisfies Over as
  4. Derive Types From Values
  5. Make Invalid States Impossible to Represent
  6. Use Exhaustive Checks With never
  7. Use as const for Constants
  8. Use Type Predicates
  9. Build New Types From Existing Types
  10. Validate External Data at Runtime
  11. Avoid enum in Most Cases
  12. Prefer Inferable Generics
  13. Enable Strict Compiler Options
  14. Learn Template Literal Types
  15. Type Safety ≠ Runtime Safety
  16. Use Branded Types to Model Nominal Types
  17. Preserve Literals With const Type Parameters
  18. Control Inference With NoInfer<T>

Prefer unknown Over any

A lot of type safety starts here.

unknown forces you to prove what a value is before using it. any skips the type system entirely, allowing unsafe operations to spread through your code.

function parse(data: unknown) {
  if (typeof data === "string") {
    return data.toUpperCase();
  }
}

Why it matters

  • Forces validation before use
  • Preserves type safety
  • Prevents unsafe type leakage

Table of Contents

Let Type Inference Do the Work

The best TypeScript code relies on inference instead of restating what the compiler already knows.

const name = "Ada";

Instead of:

const name: string = "Ada";

Over-annotation

  • Widens types
  • Hurts inference
  • Creates maintenance overhead

Inference scales better.

Table of Contents

Prefer satisfies Over as

Added in TS 4.9, and one worth adopting immediately.

const routes = {
  home: "/",
  about: "/about",
} satisfies Record<string, string>;

Instead of:

const routes = {
  home: "/",
  about: "/about",
} as Record<string, string>;

satisfies checks that a value matches a type while preserving its inferred type.

Use satisfies when validating object shapes. Reserve as for when the compiler can't figure it out on its own.

Table of Contents

Derive Types From Values Instead of Duplicating Them

One of the biggest TypeScript mindset shifts.

const roles = ["admin", "user", "guest"] as const;

type Role = (typeof roles)[number];

Single source of truth. Change the runtime values and the type updates with them.

Table of Contents

Make Invalid States Impossible to Represent

Good TypeScript models don't just describe data. They prevent impossible combinations.

Discriminated unions are the cleanest way to enforce these constraints.

type State =
  | { status: "loading" }
  | { status: "success"; data: User }
  | { status: "error"; error: Error };

These models scale much better than loose optional property blobs because invalid states can't be represented.

Future refactors become safer because the compiler ensures every valid state is handled.

Table of Contents

Use Exhaustive Checks With never

Once you've modeled your states as a discriminated union, exhaustiveness checking ensures every case is handled.

function render(state: State) {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data;
    case "error":
      throw state.error;
    default: {
      const exhaustive: never = state;
      return exhaustive;
    }
  }
}

Add a new state to the union, and the compiler immediately points out every place that needs updating.

Table of Contents

Use as const for Configuration and Constants

Without as const:

const theme = {
  mode: "dark",
};

mode becomes string.

With as const:

const theme = {
  mode: "dark",
} as const;

Now it becomes 'dark'.

A small change that makes a real difference for config objects and constants.

Table of Contents

Use Type Predicates for Reusable Narrowing

Type predicates let a runtime check teach the compiler something.

function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

Then:

if (isUser(data)) {
  data.id;
}

Most useful at API and input boundaries.

Table of Contents

Build New Types From Existing Types

Think in transformations instead of duplication.

type UserPreview = Pick<User, "id" | "name">;

Learn these utility types

  • Pick
  • Omit
  • Partial
  • Required
  • Indexed access types

They pay off more as the codebase grows.

Table of Contents

Validate External Data at Runtime

TypeScript does not validate API responses.

This is one of the most misunderstood parts of TypeScript.

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
});

Every API response, form submission, environment variable, JSON file, and user input is an untrusted boundary.

TypeScript can't validate external data; you need runtime validation for that.

Table of Contents

Avoid enum in Most Cases

Usually simpler:

const roles = ["admin", "user"] as const;

Than:

enum Role {
  Admin,
  User,
}

In most application code, literal unions are easier to refactor, serialize, and work with than enums.

Enums have their place, but they're usually overkill.

Table of Contents

Prefer Generics That Infer Automatically

Great TypeScript APIs rarely require manual generic arguments. Design them so the type infers from what callers pass in.

Caller has to specify the type manually:

getData<User>("/api/user");

T infers from the schema, nothing to annotate:

getData("/api/user", userSchema);

If callers are constantly writing <SomeType>, that's usually a sign the API could do more of the work.

Table of Contents

Turn On the Strict Compiler Options

Many teams use TypeScript in "autocomplete mode."

Strict mode is where TypeScript really starts paying off.

{
  "strict": true,
  "noUncheckedIndexedAccess": true,
  "exactOptionalPropertyTypes": true
}

strict is the baseline. The other two aren't covered by it, and they catch a real class of bugs that strict alone misses.

Table of Contents

Learn Template Literal Types

Underused, and worth learning.

type Route = `/api/${string}`;

Excellent for:

  • Routes
  • Event names
  • CSS utilities
  • Design systems
  • Query keys

Once you start using them, they show up everywhere.

Table of Contents

"Type-Safe" Does Not Mean "Runtime Safe"

This compiles:

const user = (await response.json()) as User;

But it may still fail at runtime.

TypeScript improves correctness, but it isn't a runtime safety net.

  • It does not validate external data
  • It does not guarantee good architecture
  • It does not eliminate runtime bugs

Use TypeScript to model your program well. Then validate anything that comes from the outside world.

Table of Contents

Use Branded Types to Model Nominal Types

TypeScript is structural, so two types with the same shape are interchangeable even when they mean different things.

type UserId = string;
type OrderId = string;

function getUser(id: UserId) { /* ... */ }

const orderId: OrderId = "order_123";
getUser(orderId); // No error, but almost certainly a bug

A brand adds a compile-time-only tag that makes them distinct:

type UserId = string & { readonly __brand: "UserId" };
type OrderId = string & { readonly __brand: "OrderId" };

function getUser(id: UserId) { /* ... */ }

declare const orderId: OrderId;
getUser(orderId); // Error: OrderId is not assignable to UserId

Zero runtime cost, and look-alike primitives can't be swapped by mistake.

Table of Contents

Preserve Literals With const Type Parameters

Without const, generic parameters widen to their base type:

function first<T extends readonly unknown[]>(arr: T) {
  return arr[0];
}

first(["a", "b", "c"]); // string

With the const modifier, the literal comes through:

function first<const T extends readonly unknown[]>(arr: T) {
  return arr[0];
}

first(["a", "b", "c"]); // "a"

Good for any API where the exact shape matters, without making callers remember as const.

Table of Contents

Control Inference With NoInfer<T>

TypeScript infers T from every argument, so a loose argument site can silently widen the type:

function pick<T>(values: T[], fallback: T): T { ... }

pick(["a", "b"], 42); // T infers as string | number, no error

NoInfer<T> tells the compiler to skip that argument when solving for T:

function pick<T>(values: T[], fallback: NoInfer<T>): T { ... }

pick(["a", "b"], 42); // Error: 'number' is not assignable to 'string'

Most common with default values, fallbacks, and validation callbacks.

Table of Contents

Projets similaires

Cheatsheets for experienced React developers getting started with TypeScript

TypeScriptcheatsheetguidereact
Ttypescript-cheatsheets
47,1 k étoiles4,3 k

A curated list of awesome 🔥 TypeScript Tips 🔥

awesome-listhacktoberfesttips-and-tricks
Jjellydn
441 étoiles86

📜 33 JavaScript concepts every developer should know.

JavaScriptangularconceptses6
Lleonardomso
66,5 k étoiles9,1 k