Skip to main content
Guides Skills and frameworks TypeScript Interview Cheatsheet in 2026 — Patterns, Examples, Practice Plan, and Common Traps
Skills and frameworks

TypeScript Interview Cheatsheet in 2026 — Patterns, Examples, Practice Plan, and Common Traps

9 min read · April 25, 2026

A practical TypeScript interview cheatsheet covering the patterns interviewers actually test in 2026, with examples, traps, talking points, and a seven-day practice plan.

TypeScript interview cheatsheet in 2026 searches usually come from candidates who already know JavaScript and need the high-leverage patterns: how to model data, narrow safely, write useful generics, avoid any, and explain type tradeoffs without turning every answer into type golf. This guide focuses on the patterns interviewers actually test, the examples worth practicing, a prep plan, and the common traps that make TypeScript code look confident but unsafe.

TypeScript interview cheatsheet in 2026: what to know first

Interviewers use TypeScript to evaluate two things at once: whether you understand JavaScript runtime behavior and whether you can use static types to make code easier to change. The strongest answers keep those separate. Types disappear at runtime. They help editors, compilers, reviewers, and future maintainers reason about values before the code runs.

Expect TypeScript to appear in several interview formats:

  • Frontend coding rounds with React props, event handlers, forms, and async data.
  • Backend Node.js rounds with request/response models, validation, and service boundaries.
  • Full-stack take-homes where API types, generated clients, and domain models matter.
  • Library or platform rounds with generic utilities, type-safe builders, and public APIs.
  • Debugging rounds where an unsafe assertion or overly broad type hides a bug.

You do not need to memorize every utility type. You do need to explain why a type expresses the domain, where runtime validation is still required, and how to keep the type system helpful instead of decorative.

Core mental model: TypeScript checks shapes, not intentions

TypeScript is structurally typed. If an object has the required shape, it can satisfy an interface even if it was not explicitly declared as that interface. That is powerful for JavaScript interoperability but important in interviews.

type User = { id: string; email: string };

const adminLike = { id: "u1", email: "a@example.com", role: "admin" };
const user: User = adminLike; // OK: extra fields are allowed through assignment

A good explanation: “TypeScript cares that adminLike has at least the fields required by User. Extra fields are fine once the value is in a variable. Excess property checks are stricter for object literals because they catch likely mistakes.”

Also be clear that TypeScript does not validate JSON from an API. If data crosses a trust boundary, use runtime validation or explicit parsing. The type should represent the result after validation, not wishful thinking about the raw input.

Narrowing and discriminated unions

Narrowing is one of the highest-signal topics. Interviewers want to see whether you can safely move from broad inputs to specific cases.

type Loading = { status: "loading" };
type Success = { status: "success"; data: User[] };
type Failure = { status: "failure"; error: string };
type UsersState = Loading | Success | Failure;

function renderUsers(state: UsersState) {
  switch (state.status) {
    case "loading":
      return "Loading";
    case "success":
      return state.data.map(u => u.email).join(", ");
    case "failure":
      return state.error;
    default:
      const exhaustive: never = state;
      return exhaustive;
  }
}

The discriminant field, here status, lets TypeScript know which fields exist. The never check catches future cases when someone adds status: "empty" and forgets to update the switch. In interviews, say this is better than a loose shape such as { loading?: boolean; data?: User[]; error?: string }, because that shape permits impossible states like loading and error at the same time.

Use unknown for values you have not validated. Use type guards or schema validation to narrow.

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

In production, a validation library may be cleaner. In an interview, the key is acknowledging that runtime checks are required.

Generics that earn their keep

Generics are not for making code look advanced. They are for preserving relationships between inputs and outputs.

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

const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined

The generic matters because the output type depends on the input type. A weak generic is one that does not preserve a relationship:

function parseJson<T>(text: string): T {
  return JSON.parse(text) as T;
}

This compiles but is unsafe because the caller can choose any T. A stronger answer says: “This is a type assertion hidden behind a generic. I would return unknown and validate, or require a parser.”

function parseWith<T>(text: string, decode: (value: unknown) => T): T {
  return decode(JSON.parse(text));
}

Common generic interview tasks include typed pick, typed event emitters, API clients, form builders, and reusable table components. The pattern is the same: use keyof, indexed access, and constraints to keep the relationship honest.

function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

Explain K extends keyof T as “the key must be one of the actual keys of T, and the return type is the property type for that key.”

Utility, mapped, and conditional types

You should know the common utilities and what problem they solve:

| Utility | Use it for | Trap | |---|---|---| | Partial<T> | Patch/update objects | Makes every field optional, which may hide required domain rules | | Required<T> | Normalize optional fields | Does not create values at runtime | | Pick<T, K> | Public view or subset | Can drift from domain intent if overused | | Omit<T, K> | Create input type without generated fields | Can leak fields if source type changes unexpectedly | | Readonly<T> | Prevent mutation through type | Not deep runtime immutability | | Record<K, V> | Map known keys to values | Broad string keys may imply more completeness than exists | | ReturnType<F> | Reuse function output type | Can couple callers to implementation details | | Awaited<T> | Unwrap promise results | Still need error modeling |

Mapped types are useful when you transform all fields in a shape:

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

Conditional types are useful for type-level branching, but they are easy to overdo. A good interview line: “I’ll use conditional types when they make a public API safer, not just to compress code. If a teammate cannot debug the type, it may be the wrong abstraction.”

Async and API boundaries

TypeScript shines at boundaries, but only if you model both success and failure. A practical pattern is a result type:

type Result<T, E = string> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
  const raw: unknown = await res.json();
  if (!isUser(raw)) return { ok: false, error: "Invalid user payload" };
  return { ok: true, value: raw };
}

This avoids pretending every failure is an exception or every response is valid. In a React interview, this maps cleanly to discriminated UI states. In a backend interview, it makes service contracts explicit.

Also be precise about Promise<T[]> versus T[], T | undefined versus T | null, and optional properties. Optional means the property may be absent. Nullable means the property is present but can be null. They are not the same domain statement.

Common TypeScript traps

The biggest trap is any. It disables checking and spreads. If you need a safe placeholder, use unknown and narrow. If you truly need any at a boundary, quarantine it in one small function and return a validated type.

The second trap is unsafe assertions. value as User tells the compiler to trust you. It does not check the value. Use assertions sparingly and explain why they are safe.

The third trap is over-broad object types. { [key: string]: string } says every string key returns a string, which may not be true. Record<string, T> can be useful, but be careful with missing keys.

The fourth trap is optional field confusion. foo?: string behaves like foo may be undefined. If exact optional property semantics are enabled, assignment behavior gets stricter. State whether the field can be omitted, null, or empty string.

The fifth trap is type-level cleverness that hurts maintainability. If a conditional type saves four lines but confuses the team, it may fail the interview for senior roles. Types are part of the API and should be readable.

The sixth trap is forgetting runtime JavaScript. TypeScript will not stop a race condition, mutation bug, timezone bug, or bad network response by itself.

How to talk about TypeScript in interviews

Use plain language. Instead of saying “I’ll make this generic,” say “The output type should depend on the input key, so I’ll use a constrained generic.” Instead of saying “I’ll cast it,” say “This crosses a trust boundary, so I’ll validate and only then return the domain type.”

For senior roles, talk about migration and standards. Enable strict when possible. Add noImplicitAny, strictNullChecks, and safer compiler options. Do not convert a large JavaScript codebase by adding thousands of anys. Start at boundaries, high-change modules, and shared libraries. Use generated API types carefully, and still validate untrusted data when it enters the system.

When reviewing TypeScript code, ask: Does the type encode a real invariant? Does it prevent impossible states? Does it expose too much implementation detail? Is runtime validation present at boundaries? Would a new teammate understand the type in six months?

Seven-day TypeScript practice plan

Day 1: Practice narrowing. Write discriminated unions for loading/success/error, payments, feature flags, and form states. Add exhaustive never checks.

Day 2: Practice generics. Implement first, groupBy, get, pick, and a typed event emitter. Explain what relationship each generic preserves.

Day 3: Practice API boundaries. Parse unknown JSON into domain types. Return result types and model validation errors.

Day 4: Practice React or Node examples. Type component props, async hooks, request handlers, and middleware without overusing React.FC or any.

Day 5: Study utility types. Recreate Pick, Omit, Partial, and Record in simple form so you understand the mechanics.

Day 6: Debug bad TypeScript. Remove unsafe assertions, replace any with unknown, split impossible states, and simplify clever conditional types.

Day 7: Run a mock. Explain every type decision out loud: what invariant it encodes, where runtime validation happens, and what tradeoff you are making.

Final interview checklist

  • Can I explain structural typing and excess property checks?
  • Did I use unknown for untrusted data and narrow it?
  • Did I avoid impossible states with discriminated unions?
  • Did my generic preserve a real input/output relationship?
  • Did I avoid any and unsafe assertions except in tiny, justified places?
  • Did I model async success and failure clearly?
  • Did I distinguish optional, undefined, null, and empty values?
  • Did I keep types readable for the next engineer?

TypeScript interviews reward pragmatic type safety. If you show that types are a design tool, not decoration, you will look stronger than candidates who can write advanced conditional types but cannot explain what bugs they prevent.