TypeScript Interview Questions & Answers
Curated TypeScript interview questions with detailed answers, grouped by topic. Search, revise and get interview-ready.
TypeScript Introduction
TypeScript is a typed superset of JavaScript — every valid JavaScript program is also valid TypeScript. It adds optional static types, which are checked at compile time and then compiled away into plain JavaScript.
No. TypeScript must be compiled to JavaScript first (by tsc or a bundler’s TypeScript integration) before it can run in a browser or Node.js.
They are completely erased during compilation. The compiled JavaScript output contains no type information at all, so TypeScript adds zero runtime performance overhead.
TypeScript Setup
tsc (the TypeScript compiler), e.g. npx tsc file.ts, or simply npx tsc to compile an entire project based on its tsconfig.json.
It configures how the compiler behaves for a project — which files to include, which JavaScript version to target, and which strictness rules to enforce.
Editors like VS Code run the TypeScript language service in the background, which continuously type-checks your code and reports errors live as you type.
Basic Types
No — TypeScript has a single number type that covers all numeric values, whether whole numbers or decimals.
It makes null and undefined distinct types that are not automatically assignable to other types unless explicitly included, e.g. string | null — preventing a large class of "cannot read property of undefined" runtime errors.
Either number[] or the equivalent generic form Array<number>.
Arrays & Tuples
An array type describes a list of elements all sharing the same type, of any length. A tuple has a fixed length, with each position given its own specific type.
[number, number] — the first and second elements are each typed individually, and the tuple must have exactly two elements.
It allows a variable number of trailing elements of a given type after the fixed positions, e.g. [name: string, ...scores: number[]].
Enums
They auto-increment starting from 0, unless a custom starting value is given, in which case subsequent members increment from there.
No — every member of a string enum must be given an explicit value.
A union of string literal types, e.g. type Status = "PENDING" | "ACTIVE". It requires no extra runtime code (unlike a regular enum, which compiles into a real object) and integrates more naturally with plain JSON.
any, unknown, never & void
Both accept any value, but unknown requires you to narrow it to a specific type before you can use it, while any bypasses type checking entirely with no such requirement.
A value that can never occur — used for functions that always throw or never return, and for exhaustiveness checks over union types in a switch statement.
When the function doesn’t return a meaningful value and exists purely for its side effects, like logging a message.
Type Inference
No — TypeScript infers types automatically from initial values, return statements, and surrounding context (contextual typing) in most cases.
(string | number)[] — TypeScript infers a union covering every element type present in the array.
It turns what would otherwise be a silent, implicit any type (for example, an uninitialized variable with no type context) into a compile error instead, preserving type safety.
Type Aliases
It creates a reusable, named alias for any type — a primitive, object shape, union, function type, or anything else.
No — it’s just another name for an existing type. Two aliases pointing at the same underlying shape are fully interchangeable.
Yes, e.g. type MathOperation = (a: number, b: number) => number, useful for typing callbacks consistently.
Interfaces
Structural typing means an object satisfies an interface if it has the required shape, regardless of how it was created or declared — TypeScript checks shape compatibility, not explicit type names.
With the extends keyword, e.g. interface Dog extends Animal { ... }, inheriting all members of the parent interface.
With the implements keyword, e.g. class Circle implements Shape, which guarantees the class provides every member the interface requires.
Function Types
It automatically becomes optional — callers may omit it, in which case the default value is used.
Any number of remaining arguments passed to the function, gathered into a typed array.
They let a single function accept different combinations of parameter types, each with its own precise return type, by declaring multiple call signatures above one shared implementation.
Union & Intersection Types
The value can be either a string or a number — but you can only use operations valid for both until the value is narrowed to one specific type.
The value must satisfy both A and B simultaneously — it has all the members of every combined type.
A union of object types that share a common literal "tag" property (like kind: "circle"), which TypeScript can check in a conditional to automatically and safely narrow to the exact matching shape.
Literal Types
Only the exact string "success" — not any arbitrary string.
Q35. Why is a const variable typically inferred more narrowly than a let variable with the same value?
Since a const can never be reassigned, TypeScript infers its most specific literal type. A let variable is inferred more broadly (e.g. string) since it might be reassigned to a different value of the same general type later.
It locks an object or array to its most specific literal types and makes it deeply readonly, commonly used for defining fixed configuration values.
Object Types
Typing an object whose exact keys aren’t known ahead of time, but whose values all share a common type, like a dictionary — e.g. { [key: string]: number }.
An object type with string keys and number values — a more concise, commonly preferred alternative to writing an index signature by hand.
When an object literal is passed directly to a typed parameter or variable — it flags properties on the literal that don’t exist on the target type, catching likely typos.
Optional & Readonly Properties
undefined — its type becomes string | undefined.
TypeScript reports a compile-time error — readonly properties can be set once, typically during initialization, but never reassigned afterward.
No — like all TypeScript type features, readonly is a compile-time-only check. It’s erased during compilation and provides no runtime enforcement on its own.
Type Assertions
No — it only changes how the compiler treats the value’s type. No actual conversion or validation happens.
It tells the compiler a value is definitely not null or undefined, even though its declared type says it might be — a narrower, more targeted form of assertion.
TypeScript won’t catch the mistake, since the safety check was intentionally skipped — it surfaces as a runtime error instead, exactly the kind of bug TypeScript is normally meant to prevent.
Classes
Writing constructor(public name: string) both declares the class property and assigns it from the constructor argument in one step, removing repetitive boilerplate.
Q47. What must a subclass constructor call before using this, if the parent has its own constructor?
super() — it must be called first, before this can be accessed, to properly initialize the parent class.
No — an abstract class exists only to be extended. It can declare abstract methods that concrete subclasses are required to implement.
Access Modifiers
public — accessible from anywhere, including outside the class.
private members are accessible only within the declaring class itself. protected members are also accessible from subclasses, but not from outside code.
TypeScript’s private is a compile-time-only check that’s erased during compilation, with no runtime enforcement. Native # fields are truly private and enforced by the JavaScript runtime itself.
Interfaces vs Type Aliases
Declaration merging automatically combines multiple declarations with the same name into one. Only interface supports this — declaring the same type alias twice is a compile error.
Unions, intersections, tuples, and primitive aliases directly — an interface can only describe object/class shapes.
Yes, as long as the type alias describes an object shape — both interface and a compatible type alias can be used with implements.
Generics
They let a function, interface, or class stay reusable across many types while remaining fully type-safe, avoiding the alternative of using any (losing safety) or duplicating code per type.
TypeScript infers it automatically from the argument passed in — it doesn’t need to be specified manually in most cases.
Yes — both interfaces (e.g. interface ApiResponse<T>) and classes (e.g. class Box<T>) can accept type parameters, just like functions.
Generic Constraints
Because T could be any type, and TypeScript can’t guarantee every possible type has a length property — accessing it would be unsafe.
It restricts T to only types that satisfy the HasLength shape, which then safely unlocks access to the properties that shape guarantees.
That K is restricted to one of the actual property keys of T, enabling safe, precisely-typed property access like obj[key] without risking an invalid key.
Type Narrowing
Deducing a more specific type for a value within a certain block of code, based on a runtime check you’ve already performed, like typeof or instanceof.
Whether an object has a particular property — useful for narrowing unions of plain object shapes that don’t share a class hierarchy.
Because every member shares a common literal "tag" property, checking that single property narrows the entire object safely and exhaustively, with strong editor support and compile-time exhaustiveness checks.
Utility Types
It creates a new type where every property of T becomes optional — commonly used for typing partial update payloads.
Pick keeps only the listed keys K from T, while Omit keeps everything except the listed keys K.
The type of the value that function returns, extracted automatically without needing to write it out manually.
Mapped Types
It builds a new type by iterating over the keys of an existing type (or a union of literals) and applying the same transformation to each one — essentially a "map()" for types.
They are themselves just mapped types under the hood, e.g. { [K in keyof T]?: T[K] } for Partial.
Renaming each key as the type maps over it — for example, prefixing every key with "get" to build a set of getter method names.
Conditional Types
If T is assignable to U, the type resolves to X; otherwise it resolves to Y — the type-level equivalent of a ternary expression.
It introduces a new type variable inside an extends clause, letting TypeScript extract and capture part of a matched type, like a function’s return type or an array’s element type.
It automatically distributes over each member of the union individually, then combines the results back into a union — this is called a distributive conditional type.
Custom Type Guards
It’s a type predicate — if the function returns true, TypeScript narrows the checked value to the User type for the rest of that code path.
They let you safely validate and narrow unknown values — like a parsed API response — before trusting their shape, centralizing that validation logic in one reusable place.
No — it trusts the declared "is Type" return type. An incorrectly implemented guard will still compile fine, but can silently narrow to the wrong type.
Modules
No — it uses the same ES module syntax, with the addition of an optional type-only import form (import type).
That the import is completely erased from the compiled JavaScript output, since it’s only ever used for type checking, never at runtime.
ES modules — namespaces are a legacy feature that predates ES modules and don’t integrate well with modern bundlers and tree-shaking.
Declaration Files
Only type information — no runtime implementation code. It describes the shape of existing JavaScript so TypeScript can type-check code that uses it.
It hosts community-maintained type definitions (from the DefinitelyTyped project) for popular JavaScript packages that don’t ship their own built-in types.
That a value named APP_VERSION exists at runtime (perhaps injected by a build tool), without TypeScript needing to see where it was actually defined.
Decorators
Observe, modify, or replace a class, method, property, or accessor at the point it’s defined, using concise @ syntax — commonly used for logging, validation, and dependency injection.
A function that returns a decorator, allowing configuration options to be passed in, e.g. @MinLength(8) instead of a fixed, unconfigurable decorator.
Angular (@Component, @Injectable) and NestJS (@Controller, @Get), among others — most developers encounter decorators as consumers of these frameworks even if they rarely write custom ones.
tsconfig Deep Dive
target controls what JavaScript syntax the compiled output uses. lib controls which built-in type definitions (like DOM or ES2020 APIs) are available to your code — they are independent settings.
Several flags at once, including noImplicitAny, strictNullChecks, strictFunctionTypes, and strictPropertyInitialization — each catching a different category of type mistake.
It makes indexing into an object or array with an index signature return T | undefined instead of just T, correctly reflecting that the key might not actually exist — it is not included in strict by default.
TypeScript Best Practices
Enabling strict mode on a large, loosely-typed codebase later surfaces a large batch of newly-flagged errors all at once, whereas starting strict spreads that cost out naturally as each file is written.
TypeScript types are erased at runtime and don’t verify that real data actually matches — response.json() is typed as any by default, so trusting an assumed shape without validation can silently let mismatched data flow through your program.
Q90. What is the benefit of modeling application state as a discriminated union instead of many optional fields?
It makes invalid or contradictory states (like "loading" and "data present" at the same time) genuinely unrepresentable in the type system, rather than merely discouraged by convention.