TypeScript interviews in 2026 go well beyond 'explain interface vs type.' Expect questions on generics with constraints, conditional and mapped types, discriminated unions, type narrowing strategies, and how to type real-world patterns in React and Node.js. This guide covers the full scope with code examples and answer frameworks.
Start Free Practice Interview →TypeScript has moved from 'nice to have' to the default for most professional JavaScript development. The result: TypeScript interviews have gotten significantly deeper. You'll still be asked about fundamentals like union types and type guards, but you'll also face questions about typing complex patterns: generic API response wrappers, discriminated unions for state machines, conditional types that transform other types, and framework-specific patterns like typing React hooks or Express middleware.
The strongest candidates demonstrate that they use TypeScript's type system to make impossible states impossible — not just to satisfy the compiler. This guide is organized by topic area: type system fundamentals first, then generics and advanced types, practical patterns, framework-specific TypeScript, migration strategies, and behavioral questions.
Type system fluency — reading and writing generics, union and intersection types, mapped types, and conditional types. Interviewers assess whether you think in types or add them after the fact.
Type narrowing and control flow — type guards, discriminated unions, the in operator, instanceof checks, and assertion functions. Can you write code where the compiler proves correctness at each branch?
Generics and type-level programming — the dividing line between junior and senior TypeScript. Generic constraints, inference, and reusable type-safe abstractions.
Practical typing challenges — third-party libraries without definitions, dynamic object shapes, API responses, event handlers, and complex state management.
Framework-specific patterns — React prop typing, hooks, context, higher-order components. For Node.js: middleware typing, request/response types, database models.
Migration and adoption — migration strategy, handling any types, incremental adoption, and strict mode configuration.
If you're coming from a JavaScript background, understanding what TypeScript interviews add helps focus your preparation. TypeScript interviews don't replace JavaScript knowledge — they layer type system skills on top.
| Dimension | TypeScript Interview | JavaScript Interview |
|---|---|---|
| Core focus | Type system mastery, compile-time safety, using types to model domain logic | Runtime behavior, closures, prototypes, event loop, async patterns, DOM |
| Code questions | Typed functions with generics, type guards, utility types, complex typed patterns | Algorithms, data structures, async operations, closures and scope |
| Error handling | Compile-time prevention — discriminated unions, exhaustive checks, strict null handling | Runtime — try/catch, error boundaries, defensive coding, validation |
| Architecture | Type-safe API contracts, generic abstractions, module type exports, declaration files | Module patterns, dependency injection, event-driven architecture |
| Framework knowledge | Typing React components/hooks, typing Express middleware, generic API wrappers | React lifecycle, Node.js streams, Express routing, browser APIs |
| Senior topics | Conditional types, mapped types, template literal types, type-level programming | Performance optimization, memory management, build tooling, security |
These questions test your core understanding of TypeScript's type system. Every interview includes at least two or three fundamentals questions — even for senior roles.
interface and type in TypeScript? When would you use each?The most common TypeScript interview opener. Reveals whether you understand the actual technical differences versus just having a style preference.
Cover actual differences: (1) Declaration merging — interfaces merge when declared twice (essential for library augmentation), types error on redeclaration. (2) Extends vs intersection — interfaces use extends with better error messages on conflicts; types use & which silently produces never for conflicting properties. (3) Computed properties — types support mapped types and conditional types that interfaces can't. (4) Union types — only type can represent unions. Practical guidance: interfaces for object shapes that might be extended; types for unions, intersections, mapped types, and anything beyond plain object shapes.
The core differences are technical, not just stylistic. Interfaces support declaration merging — declaring interface Window twice merges them, which is essential for augmenting third-party types. Types don't merge; redeclaring is a compile error. Interfaces use extends for composition, which gives clear errors on property conflicts. Types use & intersection, which silently produces never for conflicting property types — a subtle bug source. But types can do things interfaces can't: union types, mapped types, conditional types, and template literal types. My practical rule: interfaces for object shapes that define contracts (component props, API responses, class implementations), types for everything else (unions, utility types, computed types, type-level logic).
Unions and intersections are fundamental. Interviewers want to see you understand how narrowing works, not just the syntax.
Union (A | B): value can be A or B, access only common members unless narrowed. Intersection (A & B): must satisfy both, all members accessible. Narrowing techniques: (1) typeof — narrows primitives. (2) in operator — checks property existence. (3) instanceof — narrows to class instances. (4) Discriminated unions — shared literal property as discriminant for exhaustive narrowing. (5) Custom type guards — function isUser(x: unknown): x is User. (6) Assertion functions — asserts x is User that throw on failure.
unknown and any? When would you use each?Tests whether you understand TypeScript's safety philosophy. any opts out; unknown preserves safety.
any disables type checking — assign anything, use it as anything without checks. unknown is type-safe: assign anything to it, but must narrow before using it. Use unknown for: function parameters needing validation, parsing external data (API responses, JSON.parse), catch blocks. Use any only for: gradual JS migration, temporarily while prototyping. Rule: prefer unknown in every case. any is a safety escape hatch; unknown is a safety checkpoint.
Null/undefined errors are the most common JavaScript runtime bug. TypeScript's strict null checking is the primary defense.
With strictNullChecks (always enable it), null and undefined are separate types. Techniques: (1) Optional properties — name?: string means string | undefined. (2) Null narrowing — if (value !== null). (3) Optional chaining — obj?.prop?.method(). (4) Nullish coalescing — value ?? default (only null/undefined, not falsy like 0 or ''). (5) Non-null assertion — value!, use sparingly. (6) Best: model nullability with discriminated unions so each state only has the properties that make sense.
Type guards bridge runtime JavaScript and compile-time TypeScript. They're how you write checks the compiler understands.
Built-in: typeof, instanceof, in, equality checks. Custom guards use the is keyword: function isString(x: unknown): x is string. The return type tells TypeScript to narrow in the truthy branch. Use for: validating API responses, discriminating complex types without literal discriminants, replacing as assertions with runtime-checked narrowing. Important: the compiler trusts your guard — if you return true incorrectly, you've introduced a lie TypeScript can't catch.
Generics are the dividing line between basic and advanced TypeScript. These questions test whether you can build reusable, type-safe abstractions — the skill that separates developers who use TypeScript from developers who think in TypeScript.
Generics are the foundation of reusable type-safe code. Tests whether you understand them deeply enough to use them effectively.
Generics parameterize types — write logic once, type system fills in specifics. Key concepts: (1) Type parameters — function identity<T>(value: T): T preserves input type in output. (2) Constraints — <T extends HasId> limits what T can be. (3) Inference — TypeScript usually infers T from usage. (4) Default parameters — <T = string> provides fallback. Use for: API response wrappers, collection utilities, factory functions, anything where the specific type varies but structure doesn't.
The real power is in constraints and inference. For example, a function that extracts a property from an object: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]. TypeScript infers both T and K from the arguments, the return type is correctly narrowed (string for a string property, number for a number property), and passing an invalid key is a compile error. I use generics most for: API response wrappers where the response shape varies by endpoint, collection utility functions preserving element types, factory patterns where the created type comes from input, and React components with configurable data shapes.
Conditional types are the most powerful feature in TypeScript's type system. Understanding them signals senior-level capability.
Syntax: T extends U ? X : Y. Key patterns: (1) Type filtering — NonNullable<T> = T extends null | undefined ? never : T. (2) Type extraction with infer — ReturnType<T> = T extends (...args: any[]) => infer R ? R : never. The infer keyword captures a type from a pattern match. (3) Distributive behavior — when T is a union, the conditional distributes over each member. (4) Recursive conditional types — for deeply nested structures like unwrapping nested Promises.
Mapped types are how TypeScript transforms existing types — the basis for Partial, Required, Readonly, and most utility types.
Mapped types iterate over keys and transform each property: { [K in keyof T]: NewType }. Built-in examples: Partial<T>, Required<T>, Readonly<T>, Pick<T, K>, Omit<T, K>. Modifiers: +? and -? add/remove optional. Custom example: type PartialNullable<T> = { [K in keyof T]?: T[K] | null } makes all properties optional AND nullable. Combine with other utility types: type RequireSome<T, K extends keyof T> = Partial<T> & Required<Pick<T, K>> makes specific properties required.
Template literal types enable string-level type safety. Tests whether you're current with the language.
Create string types from combinations: type Greeting = `hello ${string}`. Practical uses: (1) Event names — type HandlerName = `on${Capitalize<keyof EventMap>}` produces 'onClick' | 'onFocus'. (2) CSS property typing — type CSSLength = `${number}${'px' | 'rem' | 'em'}`. (3) API route typing — ensuring URL patterns match. (4) Combined with mapped types — creating getter/setter types from property names.
keyof and typeof work at the type level?These operators bridge runtime values and compile-time types. Essential for generic, DRY type definitions.
keyof T produces a union of T's property names as string literals. typeof value (at type level) extracts the type of a runtime value. Together they derive types from code: const STATUS = { pending: 'PENDING', active: 'ACTIVE' } as const; type StatusKey = keyof typeof STATUS gives 'pending' | 'active'. Patterns: keyof for property access safety in generic functions, typeof for deriving types from config objects and constants.
These questions test your ability to apply the type system to real-world problems — the patterns you'd use daily in production TypeScript code.
Discriminated unions are TypeScript's most powerful pattern for state that has different shapes depending on context. This is how you make impossible states impossible.
A discriminated union uses a shared literal property (discriminant) to differentiate variants. TypeScript's control flow narrows based on it. Benefits: (1) Each variant only has properties that make sense for it — no misleading optional properties. (2) Switch on the discriminant is exhaustively checked. (3) IDE autocomplete works perfectly per branch. Classic example: type AsyncData<T> = { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }. The data field only exists when status is 'success'. You can't accidentally access it in the loading state.
Discriminated unions are my go-to for any state with multiple modes. Compare the optional-properties approach with discriminated unions: the optional-properties version ({ loading: boolean; data?: User; error?: Error }) allows invalid combinations — loading=true with data set, or both data and error present. The discriminated union ({ status: 'loading' } | { status: 'success'; data: User } | { status: 'error'; error: Error }) makes those states literally unrepresentable. I also add an exhaustive check helper: function assertNever(x: never): never { throw new Error('Unexpected: ' + x); }. In the default case of a switch, calling assertNever(state) becomes a compile error if any variant isn't handled — so adding a new variant forces you to handle it everywhere.
Function overloads are common in real-world TypeScript but have sharp edges.
Multiple call signatures, single implementation. The implementation signature must be compatible with all overloads but isn't visible to callers. Use when: return type depends on input type in ways generics can't express, or different parameter counts map to different behaviors. Alternative: union types or generic conditional returns are often simpler. Example: function parse(input: string): string[]; function parse(input: string[]): string; — the return type depends on the input type.
A daily reality in TypeScript development. Tests practical workflow.
Escalating approach: (1) Check DefinitelyTyped — npm install @types/library-name. (2) Check if the library ships its own types. (3) Create a .d.ts file with declare module 'library-name' describing the API surface you use. Start minimal, expand as needed. (4) Use any as last resort — but isolate untyped code in a wrapper module so any doesn't leak.
satisfies operator. How does it differ from annotations and assertions?The satisfies operator (TS 4.9+) solves a real problem. Tests whether you're current with the language.
The problem: annotations widen the type, assertions bypass checking. satisfies validates a value matches a type while preserving the narrower inferred type. Annotation const x: Theme = value widens to Theme. Assertion const x = value as Theme trusts blindly. Satisfies const x = value satisfies Theme validates AND preserves literals. So theme.primary is type 'red' not Color.
Exhaustive checking is one of the strongest safety guarantees TypeScript provides.
Two approaches: (1) The never trick — in the default branch, assign the discriminant to const _exhaustive: never = action. If any case is unhandled, TypeScript errors because the type isn't actually never. (2) Return type enforcement — with noImplicitReturns enabled, a function that returns from each case will error if a case is missing. The never trick is more explicit and works even in void functions.
Most TypeScript roles are framework-specific — React, Node.js, or both. These questions test whether you can apply the type system to the patterns that matter in each framework.
React + TypeScript is the most common interview context. Prop typing is foundational.
Core patterns: (1) Props interface for each component. (2) Children — React.ReactNode for anything renderable, React.ReactElement for JSX. (3) Events — React.MouseEvent<HTMLButtonElement>, React.ChangeEvent<HTMLInputElement>. (4) Default props — destructuring defaults in function signature. (5) Extending HTML elements — React.ComponentPropsWithoutRef<'button'> gives all native button props. Pattern: interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> { variant: 'primary' | 'secondary'; isLoading?: boolean }.
Hook typing is a daily task. Getting it wrong leads to subtle null handling bugs.
useState: infers from initial value. Provide explicit generic when initial doesn't represent full type — useState<User | null>(null). useRef: two patterns. useRef<HTMLDivElement>(null) for DOM refs (readonly .current, RefObject). useRef<number>(0) for mutable values (writable .current, MutableRefObject). Custom hooks: type return explicitly, especially tuples — return [value, setValue] as const or define return type.
Express typing is notoriously tricky. Tests backend TypeScript skill.
Core patterns: (1) Route handler — Request<Params, ResBody, ReqBody, Query> generics for type-safe params and body. (2) Response typing — ensure handlers return expected shape. (3) Middleware augmentation — declaration merging to add req.user after auth middleware via declare global { namespace Express { interface Request { user?: AuthUser } } }. (4) Error handler — four-parameter (err, req, res, next) signature.
Combines generics, inference, and architecture. Common senior-level question.
Design a client where the endpoint determines request and response types automatically. Define a route map type pairing endpoints with their types, then a generic fetch function that infers everything. Example: interface ApiRoutes { 'GET /users': { response: User[]; params: { limit?: number } }; 'POST /users': { response: User; body: CreateUserInput } }. Then async function api<R extends keyof ApiRoutes>(route: R, options?: Omit<ApiRoutes[R], 'response'>): Promise<ApiRoutes[R]['response']>. Callers get full type safety with zero annotations.
Many companies are mid-migration from JavaScript to TypeScript. These questions test whether you can plan and execute a migration pragmatically — without blocking feature development.
JS-to-TS migration tests both technical skill and project management judgment. The wrong approach blocks development for months.
Phased approach: (1) Enable alongside JS — allowJs: true, .js and .ts coexist. No big-bang rewrite. (2) Add // @ts-check to existing JS files for gradual checking. (3) Convert incrementally — start with leaf modules (utilities, helpers), work inward. (4) Start loose, tighten over time — strict: false initially, enable checks one at a time. (5) Set the any boundary — untyped code at edges gets any, new code must be typed. Boundary shrinks over time. (6) Prioritize high-churn files.
I approach migration as gradual adoption, not rewrite. Phase one: add TypeScript to the build alongside JavaScript — allowJs: true, strict: false. Every existing .js file works unchanged, new files can be .ts. Takes a day, blocks nobody. Phase two: annotate boundaries — add type declarations for the most-used internal APIs. Phase three: incremental conversion starting with leaf modules. I prioritize files that change frequently since those benefit most from type checking. Phase four: tighten strictness one flag at a time. strictNullChecks first (catches the most bugs), then noImplicitAny, then the rest. Each flag is a separate PR. The key principle: at every point the codebase is fully functional. No feature branch converting everything for two months.
Strict mode is a collection of flags that significantly increase safety. Tests whether you know what each flag does.
strict: true enables: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict. New projects: always enable. Existing projects: enable incrementally. Biggest wins: strictNullChecks and noImplicitAny catch the most real bugs. Some flags like strictPropertyInitialization can be noisy with dependency injection or ORM patterns.
The central trade-off in TypeScript adoption. Tests pragmatic decision-making.
Types have diminishing returns. Strategy: (1) Type boundaries aggressively — API contracts, props, function signatures, module exports. (2) Let inference work internally — don't annotate what the compiler knows. (3) Accept any at edges — isolate in wrappers. (4) Invest in types that prevent bug categories — discriminated unions, branded types, readonly. (5) Don't type-golf — if a type takes 20 minutes to write and is incomprehensible, simplify. The point is productivity, not cleverness.
TypeScript behavioral questions focus on how you work with types in a team context — advocating for safety, reviewing typed code, and making pragmatic decisions about strictness.
Concrete examples of type system value are more convincing than abstract arguments.
STAR format. Best examples: a refactoring caught across 50+ files by the compiler, a null access caught by strict null checks, a discriminated union preventing an impossible state, a generic catching a wrong parameter. Show you noticed the save and can quantify impact.
TypeScript adoption is often a team decision. Tests leadership and communication.
Principles: (1) Address concerns directly — 'slows me down,' 'too verbose,' 'JS is fine' each have valid cores. (2) Show, don't tell — convert one module, demonstrate bugs caught, show IDE improvement. (3) Start with pain points — frequent null errors? Start with strictNullChecks. (4) Lower the barrier — allowJs, gradual adoption. (5) Respect the learning curve — pair programming and reviews help.
Code review is where type quality is maintained or degraded over time.
Type-specific checklist: (1) any usage — justified? Can it be narrowed? Isolated in a wrapper? (2) Type assertions (as) — safer alternative with guards? (3) Non-null assertions (!) — actually guaranteed? (4) Overly broad types — string where a union of literals would be safer. (5) Missing return types on exports — they're documentation and catch drift. (6) Generic constraints — properly constrained or using any unnecessarily?
TypeScript interviews test both type system knowledge and your ability to explain type design decisions clearly. Our AI simulator generates tailored questions, times your responses, and provides detailed feedback on technical depth and code clarity.
Start Free Practice Interview →Tailored to TypeScript developer roles. No credit card required.
Yes. TypeScript is a superset of JavaScript, so interviewers expect solid JavaScript fundamentals — closures, prototypal inheritance, the event loop, async/await, and ES6+ features. TypeScript adds a type system on top; it doesn't replace runtime behavior. Most interview questions involve writing actual code where the JavaScript underneath must be correct. If your JavaScript foundations are weak, focus on those first. See our JavaScript developer interview guide for preparation.
Prioritize in this order: type system fundamentals (interfaces, types, unions, intersections, type narrowing), generics (constraints, inference, generic functions and types), strict null handling (optional chaining, nullish coalescing, strictNullChecks), utility types (Partial, Required, Pick, Omit, Record), and discriminated unions. For senior roles, also prepare conditional types, mapped types, template literal types, and the infer keyword. Framework-specific typing (React props, hooks, Express middleware) is essential if the role specifies a framework.
Almost certainly. TypeScript interviews are code-heavy. Expect to write typed functions, implement type guards, create generic utilities, and potentially refactor untyped JavaScript into TypeScript. Some interviews include live coding in an IDE where the compiler catches errors in real time — so you need to be comfortable writing types that compile, not just types that look right.
For most roles, working knowledge of tsconfig.json — especially strict mode flags, module resolution, and target settings — is sufficient. Senior roles and library/framework authors may face questions about declaration files, module augmentation, and advanced compiler options. Understanding what strict mode enables and why each flag matters is expected at all levels.
Junior roles focus on fundamentals: basic annotations, interfaces vs types, unions, nullable handling. Mid-level adds generics, type guards, utility types, and framework-specific patterns. Senior expects advanced type-level programming (conditional types, mapped types, infer), migration strategy experience, type system design for library abstractions, and pragmatic decisions about where strict typing adds value.
For senior roles, yes. You may design a type-safe API client, typed state management, or a generic component library. These test whether you can use the type system architecturally — how types flow through a system across module boundaries.
Upload your resume and the job description. Our AI generates targeted questions based on the specific role — covering type system fundamentals, generics, framework-specific patterns, and migration strategy. Practice with timed responses, camera on, and detailed scoring on both technical accuracy and explanation clarity.
Start Free Practice Interview →Personalized TypeScript developer interview prep. No credit card required.