TypeScript Cheat Sheet: Syntax, Utility Types, and Everyday Patterns
cheat sheettypescript syntaxutility typesreferenceadvanced typescript

TypeScript Cheat Sheet: Syntax, Utility Types, and Everyday Patterns

TTypeScript Website Editorial
2026-06-08
9 min read

A practical TypeScript cheat sheet covering syntax, utility types, and reusable patterns for everyday app development.

A good TypeScript cheat sheet is not a list of syntax trivia. It is a compact reference for the patterns you reach for during real work: modeling data, narrowing unknown values, composing utility types, and keeping type complexity from overwhelming a codebase. This guide is designed as a practical quick reference you can revisit whenever TypeScript adds features or your team adopts new conventions. It focuses on the parts of the language that pay off repeatedly in production code, with examples that are small enough to remember and specific enough to apply.

Overview

This article gives you a working TypeScript quick reference for syntax, utility types, and everyday patterns. The goal is not to cover every feature. It is to help you choose the right tool quickly and avoid the patterns that create noisy type errors later.

TypeScript becomes most useful when you treat it as a modeling language, not just a lint layer for JavaScript. The core question is usually: what should this value be allowed to contain, and what should the rest of the program be able to assume? Once you think that way, most everyday TypeScript falls into a few categories:

  • Describing shapes with type and interface
  • Constraining values with unions, literals, and enums or enum-like objects
  • Reusing patterns with generics
  • Refining uncertain values with narrowing and type guards
  • Transforming shapes with utility types such as Pick, Omit, and Record
  • Making impossible states harder to represent

If you are learning TypeScript basics, this cheat sheet can also work as a bridge into advanced TypeScript patterns. Many of the hardest problems in a codebase come from a few simple features used in the wrong combination, not from a lack of cleverness.

Essential syntax at a glance

const username: string = "ada";
const count: number = 3;
const active: boolean = true;

const tags: string[] = ["ts", "api"];
const pair: [string, number] = ["age", 42];

function add(a: number, b: number): number {
  return a + b;
}

const log = (message: string): void => {
  console.log(message);
};

These are the basics, but most production TypeScript work starts one step above this: combining types into stable, reusable contracts.

Core framework

This section gives you the main mental model for everyday TypeScript: define clear contracts, narrow uncertainty early, and compose types from smaller parts.

1. Choose between type and interface deliberately

Both can describe object shapes. In many teams, either is acceptable for most app code, but the distinction matters enough to be intentional.

interface User {
  id: string;
  email: string;
}

type Admin = User & {
  permissions: string[];
};

A practical rule:

  • Use interface for object-like public contracts you expect to extend or implement.
  • Use type for unions, intersections, mapped types, aliases, and more expressive composition.

Do not argue about this endlessly. Pick a team convention and reserve exceptions for clarity.

2. Model states with unions, not optional chaos

Optional properties can be useful, but they often blur valid states.

type FetchState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };

This pattern is easier to reason about than a single object like:

type BadFetchState<T> = {
  loading?: boolean;
  data?: T;
  error?: string;
};

The first version makes impossible combinations less likely. In React, API clients, and state management code, discriminated unions are often one of the most useful advanced TypeScript patterns.

3. Narrow unknown values instead of trusting them

In real applications, inputs come from APIs, forms, storage, and user actions. Treat them as uncertain until checked.

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

Use unknown when a value is not yet trusted. Use any only when you intentionally want to stop TypeScript from helping.

4. Use generics to connect inputs and outputs

Generics are most readable when they express a relationship.

function first<T>(items: T[]): T | undefined {
  return items[0];
}

That is useful. This is less useful:

function process<T, U, V>(a: T, b: U): V {
  // unclear intent
  throw new Error("Not implemented");
}

As a rule, every generic parameter should earn its place. If you cannot explain what it relates, remove it.

5. Reach for utility types before writing custom ones

TypeScript includes utility types that solve common transformation problems cleanly.

type User = {
  id: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
};

type PublicUser = Omit<User, "passwordHash">;
type UserPreview = Pick<User, "id" | "email">;
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type UserMap = Record<string, User>;

The utility types most teams use regularly are:

  • Partial<T>: all properties optional
  • Required<T>: all properties required
  • Readonly<T>: immutable properties at the type level
  • Pick<T, K>: select keys
  • Omit<T, K>: remove keys
  • Record<K, V>: key-value shape
  • Exclude<T, U>: remove union members
  • Extract<T, U>: keep matching union members
  • NonNullable<T>: remove null and undefined
  • ReturnType<T>: infer function return type

6. Prefer derived types when source types already exist

Duplicated types drift. Derived types stay closer to runtime code.

function createSession(userId: string, remember: boolean) {
  return {
    token: "abc",
    userId,
    remember,
  };
}

type Session = ReturnType<typeof createSession>;

This is especially useful in application services, reducers, and server-side helper functions.

7. Keep advanced mapped and conditional types behind simple names

Advanced types are powerful, but they can become unreadable inline.

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type ApiResult<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

When a type starts to look like code golf, move it into a named alias with an explanatory name. The goal is maintainability, not just clever inference.

Practical examples

This section turns the reference into realistic patterns you can reuse in frontend and backend TypeScript code.

Typed API responses

type ApiSuccess<T> = { ok: true; data: T };
type ApiError = { ok: false; error: string };
type ApiResponse<T> = ApiSuccess<T> | ApiError;

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

async function getProduct(id: string): Promise<ApiResponse<Product>> {
  try {
    return { ok: true, data: { id, name: "Keyboard", price: 99 } };
  } catch {
    return { ok: false, error: "Unable to fetch product" };
  }
}

This pattern keeps success and failure explicit. It also pairs well with runtime validation when you parse external data.

Safer configuration objects

type Env = "development" | "test" | "production";

type AppConfig = {
  env: Env;
  apiBaseUrl: string;
  enableLogs: boolean;
};

const config: AppConfig = {
  env: "production",
  apiBaseUrl: "https://api.example.com",
  enableLogs: false,
};

Using string literal unions for environment-like values catches mistakes that plain strings would allow.

Event maps with keyof

type Events = {
  userCreated: { id: string };
  orderPaid: { orderId: string; total: number };
};

function emit<K extends keyof Events>(eventName: K, payload: Events[K]) {
  // emit event
}

emit("userCreated", { id: "u1" });
emit("orderPaid", { orderId: "o1", total: 50 });

This is a good example of generics expressing a relationship. The event name determines the payload shape.

React props and component variants

For TypeScript with React, unions are often cleaner than deeply optional props.

type ButtonProps =
  | { kind: "link"; href: string; onClick?: never }
  | { kind: "action"; onClick: () => void; href?: never };

function Button(props: ButtonProps) {
  if (props.kind === "link") {
    return <a href={props.href}>Open</a>;
  }

  return <button onClick={props.onClick}>Run</button>;
}

This pattern communicates valid combinations clearly and improves editor help.

Building update payloads

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

type UpdateUserProfile = Partial<Omit<UserProfile, "id">>;

This is a common and sensible use of utility types. You keep the canonical type and derive a patch type from it.

Exhaustive checks for future safety

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.size ** 2;
    default: {
      const _never: never = shape;
      return _never;
    }
  }
}

The never assignment helps catch missing cases when new union members are added later.

If you work on analysis-heavy systems or complex data tooling, this style of explicit modeling also supports more explainable code paths. Related architectural ideas appear in From mined rules to developer acceptance: shipping static analysis rules for the TypeScript ecosystem and A language-agnostic static analysis model in TypeScript: implementing a MU-like graph representation.

Common mistakes

This section shows where many TypeScript guides stop too early. Knowing syntax is not enough if your type design creates friction every week.

Using any as a shortcut

any removes type checking in both directions. It can be useful at a boundary during migration, but it spreads quickly. Prefer unknown plus validation or narrowing.

Overusing optional properties

If an object has many optional fields, ask whether it actually represents multiple states. A discriminated union is often clearer and safer.

Writing types that are too clever to debug

Conditional types, recursive mapped types, and inference tricks can be impressive, but they are expensive if only one teammate understands them. Use advanced types where they remove duplication or capture a real invariant. Avoid them when they mainly compress code.

Duplicating server, client, and domain models blindly

Not every layer needs the same shape. A database row, API payload, and UI view model often differ for good reasons. Derive when practical, but keep model boundaries explicit.

Forgetting runtime reality

TypeScript does not validate incoming JSON at runtime. If data crosses a trust boundary, use runtime checks or a validation library. This matters in API typing, form handling, and config loading.

Ignoring compiler settings

Your cheat sheet is only half the story without a sane tsconfig. Teams that want stronger guarantees often revisit options like strictness, module settings, and unused checks as the project matures. Treat compiler configuration as part of your type system, not as setup noise.

For broader implementation thinking around TypeScript systems and developer-facing tooling, see Designing TypeScript dashboards from developer-analytics: presenting AI-derived metrics the right way and Bringing AI to EDA UIs: building explainable results panels with TypeScript.

When to revisit

This section gives you a practical maintenance checklist. A TypeScript cheat sheet should evolve with your project, not sit untouched after the first read.

Revisit your TypeScript syntax and utility type conventions when any of these happen:

  • Your team enables stricter compiler options
  • You move from JavaScript to TypeScript in a larger part of the codebase
  • You adopt a framework pattern that changes how types flow, such as React server components, API route conventions, or new state management patterns
  • You start validating external data more rigorously
  • Your utility types become harder to read than the runtime code they support
  • A new language feature lets you replace an older workaround

A short review routine for teams

  1. Audit your top 10 most reused types. Are they still clear?
  2. Search for any, unsafe assertions, and duplicated payload types.
  3. Check whether optional-heavy objects should become unions.
  4. Review helper generics. Remove the ones nobody can explain quickly.
  5. Align runtime validation and compile-time types at your app boundaries.

If your work includes more specialized TypeScript applications, it can also help to study how strong typing supports traceability and system design in domain-specific tools. Examples from this site include Explainable procurement: building TypeScript apps that make K–12 AI contract findings auditable, Cloud EDA orchestration with TypeScript: integrate AI-driven flows and turn-key CI for chip design, and Building a TypeScript toolchain for circuit identifier inventories and field tech workflows.

The most useful takeaway is simple: keep your TypeScript patterns boring in the best way. Prefer types that communicate intent, match runtime behavior, and stay understandable six months later. That is what turns a quick reference into a durable part of your workflow.

Related Topics

#cheat sheet#typescript syntax#utility types#reference#advanced typescript
T

TypeScript Website Editorial

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-08T06:36:07.214Z