Skip to main content
Guides Skills and frameworks TypeScript Interview Questions in 2026 — Generics, Conditional Types, and Inference
Skills and frameworks

TypeScript Interview Questions in 2026 — Generics, Conditional Types, and Inference

9 min read · April 25, 2026

A practical TypeScript interview prep guide for 2026 covering generics, conditional types, inference, narrowing, discriminated unions, utility types, React props, API types, and the traps senior candidates are expected to catch.

TypeScript Interview Questions in 2026 — Generics, Conditional Types, and Inference

TypeScript interview questions in 2026 are no longer just “what is the difference between type and interface?” Senior frontend, full-stack, platform, and design-system interviews now probe generics, conditional types, inference, narrowing, discriminated unions, API boundaries, build ergonomics, and whether you know when not to make the type system clever.

The best TypeScript candidates sound pragmatic. They can write a generic helper, explain why unknown is safer than any, model a state machine with discriminated unions, and recognize when a conditional type is making a codebase harder to maintain. The goal is not type gymnastics. The goal is safer change.

TypeScript interview questions in 2026: what to expect

Most interview loops test four layers:

| Layer | Example prompt | What they want | |---|---|---| | Fundamentals | “unknown vs any?” | Safety, narrowing, compiler trust | | Modeling | “Model API response states.” | Discriminated unions and exhaustiveness | | Generics | “Write a typed pick helper.” | Constraints, indexed access, inference | | Advanced types | “Explain conditional types.” | Trade-offs, readability, distribution |

A strong answer starts by saying what the type should protect. For example: “The type should prevent rendering a success UI unless data exists, and prevent reading an error message unless the state is error.” That is better than immediately writing a clever alias.

Question 1: “What is the difference between any and unknown?”

any opts out of type checking. You can call methods, access properties, and assign it almost anywhere. It is useful at migration edges but dangerous because errors spread silently.

unknown means “we do not know the type yet.” You must narrow it before using it. That makes it a better boundary type for parsed JSON, third-party data, postMessage payloads, and caught errors.

function handle(value: unknown) {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return "not a string";
}

Interview line: “I use unknown at trust boundaries and narrow it. I use any only when I am deliberately escaping the compiler, and I try to keep that escape local.”

Question 2: “Explain generics.”

Generics let a function, type, or class preserve information about the type it receives. They are useful when the relationship between input and output matters.

function identity<T>(value: T): T {
  return value;
}

const n = identity(42);       // number
const s = identity("hello");  // string

The useful part is not that T exists. It is that T connects the input to the output. A weak generic uses T once and adds no information.

// Usually unnecessary
function log<T>(value: T): void {
  console.log(value);
}

A stronger example:

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

Here the return type depends on the array element type.

Question 3: “Write a typed pick helper.”

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) {
    result[key] = obj[key];
  }
  return result;
}

const user = { id: "u1", name: "Ava", admin: false };
const publicUser = pick(user, ["id", "name"]);

The important pieces:

  • T represents the object.
  • K extends keyof T restricts keys to valid object keys.
  • Pick<T, K> returns an object containing only those keys.

Follow-up: “Why is there an assertion?” Because TypeScript cannot always prove that incrementally assigning into an initially empty object satisfies Pick<T, K>. The assertion is local and justified. If the interviewer pushes on runtime safety, say that types do not validate the actual keys at runtime if the keys come from untrusted input. You still need runtime validation at boundaries.

Question 4: “What are conditional types?”

Conditional types choose a type based on another type relationship.

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>; // string
type B = ElementType<number>;   // number

They are most useful for reusable type transformations: unwrap promises, extract function return types, map API shapes, or narrow library types. They are least useful when they hide business logic from human readers.

A concise explanation: “Conditional types are type-level if statements. They are powerful when modeling reusable patterns, but I avoid making product logic depend on complicated type puzzles.”

Question 5: “What is infer?”

infer introduces a type variable inside a conditional type so TypeScript can extract part of a type.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type User = UnwrapPromise<Promise<{ id: string }>>;

You can also extract function return types:

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

The interview trap is overusing infer where a simpler generic constraint would do. Explain that infer is for extraction, not ordinary annotation.

Question 6: “Explain distributive conditional types.”

Conditional types distribute over unions when the checked type is a naked type parameter.

type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]

If you want to prevent distribution, wrap the type parameter in a tuple:

type ToArrayNonDistributed<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNonDistributed<string | number>; // (string | number)[]

This is a senior-level signal because many confusing TypeScript bugs come from unexpected distribution. You do not need to memorize every corner, but you should know why Exclude, Extract, and similar utility types work on unions.

Question 7: “How do discriminated unions help UI code?”

They model mutually exclusive states safely.

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

function renderUser(state: LoadState<User>) {
  switch (state.status) {
    case "idle":
      return "Start";
    case "loading":
      return "Loading";
    case "success":
      return state.data.name;
    case "error":
      return state.error;
  }
}

The status field is the discriminant. Once you check it, TypeScript narrows the rest of the object. This prevents impossible states like { loading: true, data: undefined, error: "failed" }.

Exhaustiveness follow-up:

function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

Add it in a default case when you want the compiler to flag unhandled states after adding a new variant.

Question 8: “How does TypeScript inference work?”

TypeScript infers types from values, generic arguments, contextual types, control-flow checks, and return statements. Good API design helps inference rather than forcing callers to annotate everything.

Example:

function mapValues<T, R>(items: T[], fn: (item: T) => R): R[] {
  return items.map(fn);
}

const lengths = mapValues(["a", "abc"], value => value.length);

T is inferred as string from the array. R is inferred as number from the callback return. No explicit type arguments needed.

Common trap: adding explicit generic arguments too early can make inference worse. Prefer APIs where callers pass real values and the compiler infers relationships.

Question 9: “interface vs type?”

Both can describe object shapes. interface can be declaration-merged and is often used for public object contracts. type can represent unions, intersections, primitives, tuples, mapped types, and conditional types. Many teams use interface for extendable object contracts and type for unions and transformations.

Do not present this as a moral rule. Say: “I follow the project convention. If I need a union or conditional type, I use type. If I am defining a public object contract that may be extended, interface is fine.”

Question 10: “How do you type API responses?”

The key is separating compile-time assumptions from runtime validation. If data crosses a network boundary, TypeScript does not prove it is valid. You can define types for the expected shape, but you need validation or parsing when correctness matters.

Good answer:

“I would generate types from an OpenAPI or GraphQL schema when possible, validate unknown JSON at the boundary, and keep domain types separate from raw transport types if the API is messy. I do not want any from a fetch call flowing through the app.”

Pattern:

async function getJson(url: string): Promise<unknown> {
  const res = await fetch(url);
  return res.json();
}

Then validate and narrow before use.

Common TypeScript traps

  • Using any to silence real design issues.
  • Writing generic types that are harder to read than runtime code.
  • Forgetting that types disappear at runtime.
  • Over-modeling temporary UI state.
  • Ignoring strictNullChecks and then fighting undefined bugs.
  • Treating enums, const objects, and string unions as interchangeable without considering output size and ergonomics.
  • Believing a typed API client protects against a changed server response without validation.

How to talk about TypeScript in interviews and resumes

Weak bullet: “Used TypeScript in React app.”

Better bullet: “Modeled async UI states with discriminated unions and typed API boundaries to reduce undefined-state bugs.”

Best bullet: “Improved TypeScript reliability in a design-system package by replacing any-based props with generic component APIs, discriminated unions for variants, and strict runtime validation at external data boundaries.”

Interviewers are listening for judgment. Use generics when the input-output relationship matters. Use conditional types for reusable transformations. Use inference to make APIs pleasant. Use runtime validation where the compiler cannot help. And when a type becomes a puzzle, simplify it before it becomes everyone’s problem.

Question 11: “How do you type React components without fighting inference?”

For React and design-system interviews, the practical issue is usually props modeling. Prefer explicit prop unions when variants change required fields.

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

This prevents a component from receiving both href and onClick, or neither. It is clearer than a large interface with many optional fields and comments explaining which combinations are legal.

For generic components, keep inference friendly:

type SelectProps<T> = {
  items: T[];
  getLabel: (item: T) => string;
  onChange: (item: T) => void;
};

Callers should not need to write <Select<User>> if items already contains users. The component API should let TypeScript infer T from real values.

Question 12: “Which utility types should you know?”

Know the common built-ins and what problem they solve:

| Utility | Use | |---|---| | Pick<T, K> | Keep selected fields | | Omit<T, K> | Remove selected fields | | Partial<T> | Make fields optional, often for patches | | Required<T> | Require fields after normalization | | Record<K, V> | Map known keys to one value type | | ReturnType<T> | Reuse a function’s return type | | Awaited<T> | Unwrap promises | | Extract / Exclude | Filter union members |

The trap is applying these directly to messy domain models until the meaning disappears. Partial<User> is fine for a small form draft. It is dangerous for a business command where only specific fields may be updated. In interviews, say that utility types are tools for reducing duplication, not a substitute for naming important domain concepts.

Practical tsconfig signal

Senior candidates should mention strict mode, especially strictNullChecks and noImplicitAny. These settings change TypeScript from documentation into a real safety net. If a legacy codebase cannot enable strict mode at once, migrate by package, route, or boundary, and stop new any from spreading. That is the operational version of TypeScript maturity.