TypeScript Interview Questions and Answers

TypeScript and interview questions on TypeScript for 2026: types, generics, utility types, narrowing, satisfies, strict mode, React/Node prep with elaborate answers.

Published

Updated

Tech reviewed byDeepak Prasad

TypeScript Interview Questions and Answers

TypeScript interview questions in 2026 go past "what is a type?" Hiring teams want you to explain unknown vs any, write generic helpers, use satisfies without widening literals, design discriminated unions, and know what TypeScript does not guarantee at runtime. Interview questions on TypeScript appear in frontend, full-stack, Node.js, and Angular loops—often before framework depth because types are the contract layer for APIs and UI state.

Below are 45 questions with elaborate answers; technical sections include a strong answer sample you can say aloud. Pair this guide with CSS interview questions for HTML, cascade, and layout fundamentals, front end developer interviews for HTML, CSS, and browser fundamentals, React interview questions and React JS experienced scenarios for component typing, Angular developer interviews for DI and RxJS with TypeScript, Node.js developer interviews for server-side typing, and full stack developer interviews for shared API contracts.

NOTE
Prep target: Enable strict in a toy project, practice narrowing from unknown, implement Pick/Partial on paper, and explain when runtime validation (Zod) is still required.

Tested on: Ubuntu 25.04 (Plucky Puffin); kernel 6.14.0-37-generic; Node.js 20.18.2 for runtime guard simulations.


Interview context and how to prepare

What do TypeScript interviews actually test?

TypeScript interviews test whether you can design type-safe APIs and narrow unknown data—not recite every utility type name.

Layer What interviewers probe
Fundamentals Primitives, unions, interfaces vs types
Safety unknown, narrowing, strict mode
Generics Constraints, inference, reusable helpers
Utility types Partial, Pick, ReturnType, mapped types
Patterns Discriminated unions, type guards, satisfies
Tooling tsconfig, declarations, module resolution
Reality Compile-time vs runtime; validation at boundaries
Role Emphasis
Frontend React props, event handlers, form models
Full stack Shared DTOs, API parsing, error unions
Senior Conditional types, library typings, migration strategy
TypeScript vs JavaScript — why use TypeScript?
Aspect JavaScript TypeScript
Types Dynamic only Static checking at compile time
Errors Often runtime Many caught before deploy
IDE Good Rich autocomplete, refactor
Output Runs directly Compiles/transpiles to JS
Cost Lower setup Config, build step, learning curve

TypeScript is JavaScript with optional static types—it erases to JS; no runtime type enforcement unless you add validators.

What is a typical TypeScript interview loop?
Round Duration Focus
Screening 30 min Experience, strict mode, stack
JS/TS fundamentals 45 min Types, narrowing, async
TypeScript depth 45–60 min Generics, utilities, satisfies
Framework 45–60 min React/Angular/Node with TS
Live exercise 30–45 min Type an API client or form model
System (senior) 45 min Shared types monorepo, migration

Expect generics, utility types, and discriminated unions in most TypeScript screens—gaps there show up quickly in API and UI state modeling.

What is a realistic 3–5 week TypeScript prep plan?
Week Focus Output
1 Primitives, unions, interfaces, strict flags Enable strict on small project
2 Narrowing, guards, discriminated unions parseUser(input: unknown)
3 Generics + utility types Hand-write Pick, Partial
4 satisfies, mapped/conditional types (read) Config object with literals preserved
5 Framework + mock Type React props or Express handler

Build a typed API client with success/error union—reusable story in interviews.


TypeScript fundamentals

What is TypeScript?

TypeScript is a typed superset of JavaScript developed by Microsoft. Source .ts/.tsx files are type-checked then compiled (or stripped via transpilers) to JavaScript for execution in browsers or Node.js.

Key idea: structural typing—shapes matter, not nominal class names.

A strong answer is:

TypeScript adds static types and tooling to JavaScript—it compiles away, so I still think about runtime behavior and validation at system edges.

How does TypeScript compile to JavaScript?

Pipeline:

  1. Lexer/parser builds AST from .ts
  2. Type checker validates types (can run via tsc --noEmit)
  3. Emitter outputs .js (target ES version per tsconfig)

Tools: tsc, esbuild, swc, Babel (type strip only with @babel/preset-typescript—no type checking unless separate tsc).

A strong answer is:

tsc type-checks then emits JS to the target—I run typecheck in CI even when bundlers strip types faster, so errors don't slip through.

type vs interface — when do you use each?
interface type
Extends extends keyword Intersection &
Declaration merge Yes (same name merges) No
Unions/tuples No Yes
Mapped types No Yes

Convention: interface for object shapes that may be extended; type for unions, tuples, utilities.

A strong answer is:

interface for public object contracts and declaration merging in libraries; type for unions, discriminated results, and mapped utility aliases.

any vs unknown vs never?
Type Meaning Safe?
any Opt out of checking No—avoid in app code
unknown Something—we must narrow before use Yes entry for external data
never No possible value Unreachable code, empty union
typescript
function parseJson(raw: string): unknown {
  return JSON.parse(raw);
}

Use unknown at API boundaries; never for exhaustive switch checks.

A strong answer is:

unknown for JSON and user input until narrowed; any only in rare interop escapes; never marks impossible branches and powers exhaustiveness checking.

Union and intersection types?

Union (|) — value is one of several types:

typescript
type Status = "idle" | "loading" | "error";
type Id = string | number;

Intersection (&) — value must satisfy all types:

typescript
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

Unions need narrowing; intersections combine capabilities.

A strong answer is:

Unions model alternatives—I narrow before use; intersections combine requirements, common for mixing mixin-like shapes.

Literal types and as const?

Literal types are exact values ("GET", 42, true).

as const freezes inference to narrow literals:

typescript
const routes = ["home", "settings"] as const;
type Route = (typeof routes)[number]; // "home" | "settings"

Without as const, routes becomes string[].

A strong answer is:

as const preserves literal unions for config and route maps—without it TypeScript widens to string and I lose autocomplete precision.

Optional and readonly modifiers?
typescript
interface User {
  readonly id: string;
  name: string;
  email?: string;
}
Modifier Effect
? Property may be undefined
readonly Cannot assign after creation

Readonly<T> utility makes all properties readonly shallowly.

A strong answer is:

Optional marks presence uncertainty; readonly encodes immutability for ids and config—I don't confuse optional with nullable unless both are allowed.

Enums vs string union literals?

Enum (numeric or string):

typescript
enum Role { Admin = "ADMIN", User = "USER" }

Union literals (preferred in many codebases):

typescript
type Role = "ADMIN" | "USER";

Unions are structural, tree-shake friendly, and don't emit extra JS. Const enums have caveats with bundlers.

A strong answer is:

I prefer string union types over enums for most app code—simpler JS output and aligns with JSON APIs; enums when legacy or Angular patterns require them.

What is structural typing (duck typing)?

TypeScript compares types by shape, not declaration name:

typescript
type Point = { x: number; y: number };
function draw(p: { x: number; y: number }) { /* ... */ }
const pt: Point = { x: 1, y: 2 };
draw(pt); // OK — structure matches

Contrast with nominal typing (Java classes)—extra properties can cause excess property checks on object literals.

A strong answer is:

TypeScript cares if the shape fits—not the alias name—which is why excess property errors only bite object literals assigned directly.

What is type inference?

The compiler infers types when you omit annotations:

typescript
const n = 42;        // number
const arr = [1, 2];  // number[]
function id<T>(x: T) { return x; } // T inferred from argument

Annotate public APIs; let inference handle locals when clear.

A strong answer is:

I lean on inference inside functions but annotate exported boundaries and function parameters where inference would be too wide or unclear.

What does strict mode in tsconfig include?

"strict": true enables a bundle including:

Flag Effect
strictNullChecks null/undefined distinct
noImplicitAny Error on implicit any
strictFunctionTypes Safer function parameter checking
strictBindCallApply Typed bind/call/apply

Experienced interviews expect you to develop with strict on and explain bugs it catches.

A strong answer is:

strict with null checks is non-negotiable for me—it forces handling undefined from APIs and optional fields instead of surprise runtime nulls.


Narrowing, type guards, and safety

What is type narrowing?

Narrowing refines a broad type to a specific one in a branch:

typescript
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(0));
  }
}

Built-in narrowing: typeof, instanceof, in, truthiness, equality.

A strong answer is:

Narrowing is how I use unions safely—after typeof or a custom guard, the compiler knows which operations are legal.

typeof and instanceof guards?
typescript
if (typeof value === "string") { /* string */ }
if (value instanceof Date) { /* Date */ }
if (value instanceof Error) { /* Error */ }

typeof null is "object" (JavaScript quirk)—use explicit null checks.

A strong answer is:

typeof for primitives; instanceof for class instances—I remember typeof null is object and pair with value !== null when needed.

What is a user-defined type guard?

A function returning param is Type tells TypeScript to narrow after true:

javascript
function isUser(x) {
  return (
    typeof x === "object" &&
    x !== null &&
    "id" in x &&
    "name" in x &&
    typeof x.name === "string"
  );
}

function greet(input) {
  if (!isUser(input)) return "unknown";
  return input.name;
}

console.log(greet({ id: 1, name: "Ada" }));
console.log(greet({}));
Output

Running prints Ada then unknown—runtime checks mirror what a TypeScript is User guard would encode.

A strong answer is:

User-defined guards bundle runtime validation with a type predicate—I use them at JSON boundaries instead of casting with as.

What are discriminated unions?

Objects share a literal discriminant field for safe switching:

typescript
type Result =
  | { status: "ok"; data: User }
  | { status: "error"; message: string };

function handle(r: Result) {
  switch (r.status) {
    case "ok": return r.data.name;
    case "error": return r.message;
  }
}

Makes illegal states unrepresentable—no data on error branch.

A strong answer is:

Discriminated unions model API results and UI state—I switch on status and the compiler enforces correct fields per branch.

What are assertion functions (asserts)?
typescript
function assertIsString(x: unknown): asserts x is string {
  if (typeof x !== "string") throw new Error("not string");
}

Narrows for all code after the call if it doesn't throw—stricter than boolean guard.

A strong answer is:

asserts functions throw on failure and narrow the rest of the scope—useful in test setup and invariant checks.

as vs satisfies — what is the difference?
as satisfies
Role Assertion (override compiler) Validate shape, keep narrow inference
Safety Can lie to compiler Safer for config maps
Inference Widens to asserted type Preserves literal types
typescript
const palette = {
  primary: "#3178c6",
  danger: "#ef4444",
} satisfies Record<string, string>;
// palette.primary stays "#3178c6", not just string

A strong answer is:

satisfies checks conformance without losing literals; as is an escape hatch I avoid on external data—I validate unknown, not assert it.

What is the non-null assertion operator (!)?

Postfix ! tells compiler "this isn't null/undefined":

typescript
const el = document.getElementById("root")!;

Convenient but unsafe if DOM missing—prefer explicit checks in production code.

A strong answer is:

I rarely use ! in app code—explicit null checks or early return communicate the invariant to humans and the compiler.

What is exhaustiveness checking?
typescript
function assertNever(x: never): never {
  throw new Error("unexpected: " + x);
}

function icon(shape: "circle" | "square") {
  switch (shape) {
    case "circle": return "○";
    case "square": return "□";
    default: return assertNever(shape);
  }
}

If you add "triangle" without a case, shape in default is not never—compile error.

A strong answer is:

assertNever in default catches unhandled union members when the domain grows—cheap insurance on state machines.


Generics and utility types

What are generics?

Generics are type parameters for reusable, type-safe code:

typescript
function identity<T>(value: T): T {
  return value;
}
const n = identity(42); // number

Without generics, you'd use any and lose safety.

A strong answer is:

Generics preserve the relationship between input and output types—identity, map, Promise.then all depend on that preservation.

What are generic constraints?

T extends U limits type parameter:

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

Enables safe access while staying generic.

javascript
function getProperty(obj, key) {
  return obj[key];
}
console.log(getProperty({ id: 1, name: "Ada" }, "name"));
Output

Running prints Ada—the JS shape matches the typed helper's idea.

A strong answer is:

extends keyof T lets me write one getProperty that type-checks keys at compile time in TS and documents intent in plain JS helpers too.

keyof and indexed access types?
typescript
interface User { id: string; email: string; }
type UserKeys = keyof User;           // "id" | "email"
type Email = User["email"];           // string
type PartialUser = { [K in keyof User]?: User[K] };

Foundation for mapped types and utilities.

A strong answer is:

keyof and indexed access let me derive keys and property types from existing interfaces—base for Pick and Partial implementations.

Explain Partial, Pick, and Omit.
Utility Effect
Partial<T> All properties optional
Pick<T, K> Subset of keys
Omit<T, K> Remove keys
typescript
type UserPatch = Partial<Pick<User, "name" | "email">>;
type PublicUser = Omit<User, "passwordHash">;

Partial is shallow—nested objects need custom deep partial types deliberately.

A strong answer is:

Pick and Omit shape API DTOs; Partial for PATCH updates—I remember Partial doesn't recurse into nested objects unless I design for that.

ReturnType, Parameters, and Awaited?
typescript
async function fetchUser() {
  return { id: 1, name: "Ada" };
}
type R = Awaited<ReturnType<typeof fetchUser>>; // { id: number; name: string }
type P = Parameters<typeof fetchUser>;         // []

Extract types from existing functions without duplication—great for wrappers and mocks.

A strong answer is:

ReturnType and Awaited unwrap function and Promise types for mocks and middleware—I derive from typeof fn instead of duplicating interfaces.

Record and Readonly utility types?
typescript
type Role = "admin" | "user";
type Permissions = Record<Role, string[]>;

type FrozenUser = Readonly<User>;

Record builds object types with known keys; Readonly prevents assignment to top-level properties.

A strong answer is:

Record types dictionaries keyed by union; Readonly guards immutable snapshots—I know Readonly is shallow like Partial.

What are mapped types?

Transform properties by iterating keys:

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

Built-in utilities are implemented with mapped types + modifiers (?, readonly).

A strong answer is:

Mapped types are the meta-layer behind Partial and Pick—[K in keyof T] with optional readonly modifiers.

What are conditional types?
typescript
type IsString<T> = T extends string ? true : false;
type Flatten<T> = T extends Array<infer U> ? U : T;

infer extracts type inside conditional—used in advanced library typings.

Senior interviews may ask conceptually; daily app code uses them via utilities.

A strong answer is:

Conditional types pick types based on extends checks—infer lets libraries extract Promise resolve types inside Awaited.

Template literal types?
typescript
type EventName = "click" | "focus";
type HandlerName = `on${Capitalize<EventName>}`; // "onClick" | "onFocus"

Combine string literals at type level—CSS keys, event names, route builders.

A strong answer is:

Template literal types generate string unions from other unions—handy for typed event maps and design tokens.

How would you implement Pick from scratch?
typescript
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Same pattern as built-in Pick—interview checks understanding of keyof + mapped types.

A strong answer is:

Pick maps each selected key to its original property type—if I can write that, I understand most utility type machinery.


Tooling, frameworks, and scenarios

Important tsconfig.json options for interviews?
Option Purpose
strict Enable strict family
target / module Emit level and module system
moduleResolution node16, bundler
paths Alias imports (@/components)
declaration Emit .d.ts for libraries
skipLibCheck Faster builds; skip .d.ts check
noUncheckedIndexedAccess Safer array/index access

A strong answer is:

strict plus moduleResolution matching the bundler—I enable noUncheckedIndexedAccess on greenfield when team agrees to handle undefined from index access.

What are .d.ts declaration files?

Declaration files describe types for JavaScript without implementation:

  • .d.ts for libraries consumed by TS
  • declare module for untyped packages
  • DefinitelyTyped (@types/node, @types/react)

Allows type-checking against JS-only code.

A strong answer is:

d.ts files are the contract for JS libraries—I use @types packages or write minimal declarations when a dependency ships without types.

What are TypeScript decorators?

Decorators (Stage 3; TS 5+ with experimentalDecorators legacy vs standard) attach metadata/wrappers to classes, methods, properties.

Angular uses decorators heavily (@Component, @Injectable)—see Angular interviews.

Know legacy vs TC39 standard decorator differences in 2026 migrations.

A strong answer is:

Decorators are syntactic sugar for metaprogramming—Angular DI relies on them; I know which decorator standard our TS config targets when upgrading.

TypeScript with React — what do interviewers ask?

Common topics:

  • FC vs explicit props type — prefer explicit { name: string }
  • Event typesChangeEvent<HTMLInputElement>
  • useState inferenceuseState<User | null>(null)
  • ChildrenReactNode
  • Generic components<List<T> items={T[]} />

See React interviews for hooks depth.

A strong answer is:

I type props as plain objects, events with React's DOM generics, and avoid React.FC in new code—explicit children and default export props readability.

TypeScript with Node.js — key typing patterns?
  • Request / Response types in Express
  • Environmentprocess.env typed via declaration merge or zod
  • ESM vs CJSmoduleResolution node16, .mts extensions
  • Unknown request body until validated

Pair with Node.js interviews.

A strong answer is:

I type handlers with Request/Response, validate env and body at startup, and align module settings with whether the package is ESM or CJS.

Why is runtime validation still needed with TypeScript?

Types erase at compile time—APIs, JSON.parse, and localStorage return untrusted shapes.

Libraries: Zod, Valibot, io-ts parse at runtime and infer TS types:

typescript
const UserSchema = z.object({ id: z.string(), name: z.string() });
type User = z.infer<typeof UserSchema>;
const user = UserSchema.parse(unknownInput);

A strong answer is:

TypeScript doesn't validate wire data—I parse unknown through Zod at boundaries and let inferred types flow inward.

Scenario: Type a fetch wrapper for a REST API.

Design:

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

async function apiGet<T>(url: string, parse: (u: unknown) => T): Promise<ApiResult<T>> {
  const res = await fetch(url);
  const body: unknown = await res.json();
  if (!res.ok) return { ok: false, status: res.status, error: String(body) };
  return { ok: true, data: parse(body) };
}

Discriminated union forces callers to handle error branch.

A strong answer is:

I return ok/data or ok/error unions, parse body from unknown with a validator, and never cast fetch JSON directly to an interface.

Scenario: Model UI state to make illegal states unrepresentable.

Bad: separate isLoading, error, data booleans/strings.

Good:

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

UI switches on status—can't read data while loading.

A strong answer is:

I replace boolean soup with one discriminated status field so the compiler blocks reading data during loading or idle.

Scenario: Migrate a JavaScript codebase to TypeScript.
Phase Action
1 allowJs: true, checkJs optional
2 Rename leaf modules to .ts
3 Enable strict incrementally per package
4 Type boundaries first—API clients, config
5 Avoid mass any; use unknown + guards
6 CI tsc --noEmit gate on PR

Strangler pattern—no big-bang freeze.

A strong answer is:

I migrate from the outside in—API and shared models first—with strict on new code and allowJs for legacy until we tighten module by module.

What is covariance and contravariance (function types)?

Under strictFunctionTypes, function parameters are contravariant and returns covariant.

Practical interview point: assigning (animal: Animal) => void to (dog: Dog) => void is unsafe for parameters—explains why strict function types matter.

A strong answer is:

strictFunctionTypes rejects unsafe function parameter assignments—I mention contravariance when explaining why a broader parameter type isn't a subtype.

What is the difference between import type and regular import?
typescript
import type { User } from "./models";
import { type User, createUser } from "./models";

import type erases completely—no runtime require. Useful for verbatimModuleSyntax and avoiding circular value imports.

A strong answer is:

import type keeps type-only imports out of runtime emit—important with verbatimModuleSyntax and cleaner bundler trees.


Final prep checklist

What should you rehearse before TypeScript interviews?

Checklist:

  • unknown vs any and narrowing flow
  • interface vs type trade-offs
  • Discriminated unions + exhaustiveness
  • User-defined type guards from unknown
  • satisfies vs as
  • Generics with extends keyof T
  • Partial, Pick, Omit, ReturnType, Awaited
  • Mapped type — write Pick by hand
  • strict flags and why they matter
  • Runtime validation at API boundaries
  • One React props and one API wrapper scenario
  • Front end interviews JS refresh
  • Full stack interviews if shared types matter

A strong answer is:

I practice narrowing unknown JSON, explain satisfies on a config object, implement Pick on a whiteboard, and tie answers to a real strict-mode migration or API client I shipped.


Pattern cheat sheet (quick reference)

Need TypeScript approach
External JSON unknown → validate → narrow
Success/error API Discriminated union on status or ok
Optional PATCH body Partial<Pick<T, keys>>
Config with literals satisfies
Reuse function shapes ReturnType / Parameters
Safe property access getProperty<T, K extends keyof T>
Impossible branch assertNever(x: never)
React props Explicit object type, ReactNode children
No runtime types Zod parse at boundary

References

TypeScript documentation

On-site prep


Summary

TypeScript interviews test safe boundariesunknown, discriminated unions, generics, and satisfies—and whether you admit types do not validate runtime data without guards like Zod. Answer aloud, run the guard simulations, and compare your structure to each section. Pair with React, Angular, or full stack prep when the role continues past the type layer.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …