TypeScript Interview Questions

Real TypeScript interview questions and answers covering interfaces, generics, unknown vs any, and structural typing.

A curated set of TypeScript interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.

Fundamentals

Q: What's the difference between interface and type? Both describe object shapes and are largely interchangeable for that purpose. The differences: only type can express a union ("a" | "b"), a tuple, or alias a primitive; only interface supports declaration merging (multiple interface Foo {} blocks with the same name automatically combine into one), which matters for extending third-party library types. Many teams settle on interface for object/entity shapes and type for everything else, purely as a style convention.

Q: What's the difference between any and unknown? any completely disables type checking for a value — you can call any method or access any property on it with zero compile-time safety, defeating the purpose of using TypeScript at all. unknown also accepts any value, but the compiler refuses to let you operate on it until you've narrowed it (with typeof, instanceof, or a custom type guard) to a more specific type. unknown is the type-safe way to represent "a value of a type I don't know yet," and should be preferred over any in almost every case.

Q: What is structural typing, and how does it differ from nominal typing? TypeScript uses structural typing: two types are compatible if they have the same shape, regardless of their declared names. A language with nominal typing (like Java or C#) instead requires an explicit implements/inheritance relationship — matching shape alone isn't enough. In TypeScript, this compiles fine even though Point and Coordinate were never declared as related:

Typescript
interface Point { x: number; y: number; }
interface Coordinate { x: number; y: number; }

function printPoint(p: Point) { console.log(p.x, p.y); }

const coord: Coordinate = { x: 1, y: 2 };
printPoint(coord);   // fine — same shape, no explicit relationship needed

Generics and utility types

Q: Why use a generic function instead of typing a parameter as any[]? A generic (function first<T>(items: T[]): T) preserves the relationship between the input and output types — passing string[] guarantees a string comes back, fully checked by the compiler. Typing the parameter as any[] would compile, but you'd lose all type safety on both the input and whatever you do with the return value.

Q: What does Partial<T> do, and when would you use it? Partial<T> produces a new type where every property of T becomes optional. It's commonly used for "patch"/"update" function signatures, where a caller may only want to change a subset of an object's fields rather than provide the whole thing: function updateUser(id: number, changes: Partial<User>).

Type narrowing

Q: What is a discriminated union and why is it a preferred pattern? It's a union of object types that all share one common literal field (conventionally named kind or type) used to distinguish which member of the union you're dealing with. Switching on that field lets TypeScript automatically narrow the remaining properties in each branch, catching missing cases at compile time (especially with an exhaustiveness check in the default branch) — far safer than a bag of loosely related optional properties checked with ad hoc if statements.

Compilation and tooling

Q: Does enabling strict mode in tsconfig.json change the compiled JavaScript output? No — strict only affects compile-time checking (null checks, implicit any detection, and similar). The emitted JavaScript is functionally the same either way; what changes is how many potential bugs the compiler flags and refuses to let through before that JavaScript is even produced. This is also why type annotations, interfaces, and type-only constructs are completely erased from compiled output — TypeScript's types have zero runtime footprint.

Modules

Q: What does import type guarantee that a regular import used only for a type doesn't? It guarantees, unconditionally, that the import is erased from the compiled JavaScript and never treated as a real runtime dependency, regardless of how the imported name is used elsewhere in the file. This matters most under isolatedModules, where each file is transpiled independently without full-project context — a plain import used only as a type can be ambiguous to a single-file transpiler in a way import type explicitly resolves.

Q: Would you write a new TypeScript namespace today, and why do they still show up in some codebases? No — ES modules (import/export) solve the same organizational problem namespaces were originally built for, using a real, standard JavaScript feature that works uniformly across bundlers and Node, and supports tree-shaking, which namespaces don't. Namespaces predate ES modules becoming universal, so they still turn up in older codebases and in some older libraries' bundled type definitions — worth recognizing, not worth writing new code with.

Advanced generics and conditional types

Q: What does the infer keyword do inside a conditional type? It captures a piece of the type being checked as a new type variable, rather than only branching between two fixed outcomes — for example, T extends Promise<infer U> ? U : T extracts the value type wrapped inside a Promise. This is exactly how built-in utility types like ReturnType<T> and Awaited<T> are implemented under the hood.

Q: How does a mapped type like [K in keyof T]: T[K] actually work, and what built-in types are built from it? It iterates over every key K in keyof T (the union of T's property names) and produces a new type with one property per key, each keeping (or transforming) the original property's type via T[K]. Adding ? makes each property optional, readonly makes it read-only, and a leading - (as in -readonly or -?) strips an existing modifier instead of adding one. TypeScript's own Partial<T>, Required<T>, Readonly<T>, and Record<K, V> utility types are all mapped types built exactly this way.

Testing

Q: Why is a caught error typed as unknown in TypeScript's catch clause, and how should you handle that in a test? Because JavaScript allows throwing any value, not just an Error instance, so assuming a caught error is always an Error would be unsound. The correct pattern — in a test or anywhere else — is narrowing with instanceof (checking for a specific custom error class, for example) before accessing any error-specific property, which both satisfies the compiler and correctly handles the case where something unexpected was thrown.

Q: What real value does typing a test's mock object against the real interface it stands in for provide? If the real interface later changes shape — a renamed method, an altered return type — every mock implementing it fails to compile until updated, surfacing the mismatch immediately as a compile error instead of as a confusing runtime test failure, or worse, a silently stale mock that no longer represents what the real dependency actually does.