Top 60 TypeScript Interview Questions (2026)

The 60 TypeScript questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is TypeScript?

Key Takeaways

  • TypeScript is a statically typed superset of JavaScript that adds an optional type layer and compiles to plain JavaScript.
  • Types are erased at compile time, so nothing checks them at runtime; the checking happens while you build, not while your code runs.
  • Interviews test how well you use the type system to model real data (generics, unions, narrowing), not just how to annotate a variable.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery is evaluated too.

TypeScript is a statically typed programming language built on top of JavaScript, created and maintained by Microsoft since 2012. Every valid JavaScript file is already valid TypeScript, so it's a superset: you add type annotations, the compiler (tsc) checks them, and it emits plain JavaScript that runs anywhere JavaScript runs. The point is catching type errors while you write code instead of in production, plus editor tooling like autocomplete and safe refactoring. In interviews, TypeScript questions probe how you model data with the type system (generics, unions, narrowing, utility types) and how you reason about what the compiler can and can't guarantee, not memorized syntax. This page collects the 60 questions that come up most, each with a direct answer and runnable code. If you're still building fundamentals, the official TypeScript handbook from Microsoft is the canonical path; increasingly the first TypeScript round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
49Runnable code snippets you can practice from
45-60 minTypical length of a TypeScript technical round

Watch: Learn TypeScript - Full Course for Beginners

Video: Learn TypeScript - Full Course for Beginners (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your TypeScript certificate.

Jump to quiz

All Questions on This Page

60 questions
TypeScript Interview Questions for Freshers
  1. 1. What is TypeScript and why would you use it?
  2. 2. How is TypeScript different from JavaScript?
  3. 3. What are the basic types in TypeScript?
  4. 4. What is type inference in TypeScript?
  5. 5. What is the any type and why is it discouraged?
  6. 6. What is the difference between any and unknown?
  7. 7. What is the difference between type and interface?
  8. 8. What is a union type?
  9. 9. What is an intersection type?
  10. 10. What are literal types?
  11. 11. How do you type arrays and tuples, and how do they differ?
  12. 12. What are enums in TypeScript?
  13. 13. How do you type functions in TypeScript?
  14. 14. How do optional properties and optional parameters work?
  15. 15. What is a type assertion and when should you use one?
  16. 16. What is the difference between void and never?
  17. 17. What does the readonly modifier do?
  18. 18. How do you compile TypeScript, and what is tsconfig.json?
  19. 19. What is structural typing (duck typing) in TypeScript?
  20. 20. What is the difference between object, {}, and unknown as a type?
TypeScript Intermediate Interview Questions
  1. 21. What are generics and why are they useful?
  2. 22. What are generic constraints?
  3. 23. What is type narrowing and how does control flow analysis work?
  4. 24. What is a user-defined type guard?
  5. 25. What is a discriminated union and why is it useful?
  6. 26. Which built-in utility types should you know?
  7. 27. What is the difference between Pick and Omit?
  8. 28. What does the keyof operator do?
  9. 29. What does the typeof type operator do in TypeScript?
  10. 30. What are mapped types?
  11. 31. What are conditional types?
  12. 32. What is an index signature?
  13. 33. What does strictNullChecks do?
  14. 34. What are optional chaining and nullish coalescing?
  15. 35. How do you type classes, and what do access modifiers do?
  16. 36. How does a class implement an interface?
  17. 37. What are abstract classes and how do they differ from interfaces?
  18. 38. How do modules and imports work in TypeScript?
  19. 39. What are declaration files (.d.ts)?
  20. 40. How do you set up a new TypeScript project from scratch?
TypeScript Interview Questions for Experienced Developers
  1. 41. Why can't you rely on TypeScript types at runtime?
  2. 42. What is variance, and how does TypeScript handle covariance and contravariance?
  3. 43. How does the infer keyword work in conditional types?
  4. 44. What are template literal types?
  5. 45. What does the as const assertion do?
  6. 46. How do you get exhaustiveness checking with never?
  7. 47. What are decorators in TypeScript?
  8. 48. What is declaration merging?
  9. 49. How do you validate external data when types are compile-time only?
  10. 50. How do you write a type-safe deep property accessor with generics and keyof?
  11. 51. Where is TypeScript's type system deliberately unsound?
  12. 52. What does noUncheckedIndexedAccess do, and why enable it?
  13. 53. How would you migrate a large JavaScript codebase to TypeScript?
  14. 54. When do you use generics versus function overloads?
  15. 55. What does the satisfies operator do, and how is it different from as?
  16. 56. How do you type asynchronous code and Promises?
  17. 57. What are branded (nominal) types and why use them?
  18. 58. How do you diagnose and fix slow TypeScript compilation?
  19. 59. How does TypeScript compare to Flow, and why did it win adoption?
  20. 60. Design question: how would you type a reusable API client?

TypeScript Interview Questions for Freshers

Freshers20 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is TypeScript and why would you use it?

TypeScript is a statically typed superset of JavaScript from Microsoft. You write JavaScript plus optional type annotations, the compiler checks them, and it emits plain JavaScript that runs anywhere.

You use it to catch type errors while you write instead of at runtime, and to get better editor tooling: autocomplete, inline docs, and safe refactoring across a large codebase. On small scripts the payoff is thin; on long-lived apps it's large.

Key point: A one-line definition (typed superset of JavaScript) plus the concrete payoff (catch bugs early, better tooling) beats a feature list. Interviewers open with this to hear how you organize an answer.

Watch a deeper explanation

Video: TypeScript in 100 Seconds (Fireship, YouTube)

Q2. How is TypeScript different from JavaScript?

TypeScript adds a static type layer on top of JavaScript and a compile step; JavaScript runs directly with no types. Every JavaScript file is already valid TypeScript, so the difference is what the compiler checks before your code ever runs.

At runtime they're the same: TypeScript compiles to JavaScript and the types disappear. So TypeScript's help is entirely at build time, in the editor and the compiler, not while the program executes.

TypeScriptJavaScript
TypingStatic, checked at compile timeDynamic, checked at runtime
Build stepRequired (tsc or a bundler)None
Errors caughtMany, before runningMostly at runtime
Runs in browserAfter compiling to JSDirectly

Key point: The trap answer is 'TypeScript is faster'. It isn't; the output is the same JavaScript. The real benefit is catching errors earlier.

Watch a deeper explanation

Video: TypeScript - The Basics (Fireship, YouTube)

Q3. What are the basic types in TypeScript?

The primitives are string, number, boolean, null, undefined, bigint, and symbol, plus the special types any, unknown, void, and never. You also have arrays (number[]), tuples, objects, and enums built on top.

TypeScript often infers these, so you don't annotate everything. You annotate function parameters, return types when they aren't obvious, and anything the compiler can't figure out on its own.

typescript
let name: string = "Asha";
let age: number = 31;
let active: boolean = true;
let scores: number[] = [91, 84];
let pair: [string, number] = ["age", 31];   // tuple

Watch a deeper explanation

Video: TypeScript Crash Course (Traversy Media, YouTube)

Q4. What is type inference in TypeScript?

Type inference is the compiler working out a type you didn't write. let count = 0 is inferred as number, so you don't need let count: number = 0.

Inference keeps typed code from feeling verbose. The rule of thumb: let the compiler infer local variables and return types when they're obvious, and annotate function parameters and public boundaries where inference can't reach.

typescript
let city = "Tokyo";        // inferred as string
let total = 42;            // inferred as number
// city = 5;               // Error: number not assignable to string

Q5. What is the any type and why is it discouraged?

any switches type checking off for a value: you can call anything on it, assign anything to it, and the compiler stops complaining. It's an escape hatch for untyped code or quick migration.

It's discouraged because it's contagious and silent: an any spreads through everything it touches and hides the exact bugs TypeScript exists to catch. Reach for unknown instead when you truly don't know a type, because unknown forces you to check before using it.

typescript
let data: any = getInput();
data.foo.bar.baz();   // compiles, may crash at runtime

let safe: unknown = getInput();
// safe.foo;          // Error: must narrow first

Key point: Saying 'I reach for unknown before any' is a quick signal you write disciplined TypeScript, not JavaScript with annotations bolted on.

Q6. What is the difference between any and unknown?

Both accept any value, but they differ on what you can do next. With any, you can do anything with no checks. With unknown, you can't use the value until you narrow it to a specific type with a guard.

unknown is the type-safe replacement for any: it keeps the flexibility of accepting anything while forcing you to prove the type before you touch it.

typescript
function handle(x: unknown) {
  // x.toUpperCase();          // Error
  if (typeof x === "string") {
    return x.toUpperCase();     // OK, narrowed to string
  }
}

Key point: This pair (any vs unknown) is a favorite because it separates people who understand the type system from people who annotate around it.

Q7. What is the difference between type and interface?

Both name object shapes and can extend other types. The practical differences: interface supports declaration merging (declare it twice and the members combine), while a type alias can name anything, including unions, tuples, and primitives, which interfaces can't.

A common team rule is interfaces for object and class shapes that might get extended, and type for unions, intersections, and anything that isn't a plain object. For most everyday object shapes, either works.

typescript
interface User { name: string; }
interface User { age: number; }   // merges into { name, age }

type ID = string | number;        // only a type alias can do this
type Point = { x: number; y: number };

Key point: The 'interface merges, type does unions' distinction is the answer the question needs. Avoid claiming one is strictly better; it's about fit.

Q8. What is a union type?

A union type says a value is one of several types: string | number means it can be a string or a number. You build them with the vertical bar, and they're everywhere in real code (an id that might be a string or a number, a status that's one of a few strings).

Before you use a union safely, you narrow it: check which member you actually have with typeof, a property check, or a discriminant, and the compiler lets you use the narrowed type.

typescript
function format(id: string | number): string {
  if (typeof id === "number") return id.toFixed(0);
  return id.trim();   // here id is string
}

Q9. What is an intersection type?

An intersection type combines several types into one that has all their members: A & B is a value that satisfies both A and B at once. Where a union is 'one of these', an intersection is 'all of these together'.

You use it to merge shapes, for example combining a base entity with timestamp fields, so the result must have every property from both sides.

typescript
type Timestamps = { createdAt: Date; updatedAt: Date };
type User = { name: string };
type StoredUser = User & Timestamps;
// must have name, createdAt, and updatedAt

Q10. What are literal types?

A literal type is a type whose only value is one specific literal: the type "admin" allows only the string "admin", and 200 allows only the number 200. On their own they're rarely useful, but in a union they model a fixed set of allowed values.

This is how you get enum-like safety with plain strings: type Role = "admin" | "editor" | "viewer" rejects any other string at compile time.

typescript
type Direction = "up" | "down" | "left" | "right";
function move(d: Direction) { /* ... */ }
move("up");        // OK
// move("sideways"); // Error: not assignable

Q11. How do you type arrays and tuples, and how do they differ?

An array holds any number of elements of one type: number[] or Array<number>. A tuple is a fixed-length array where each position has its own type: [string, number] is exactly a string then a number.

Use arrays for homogeneous lists that grow, and tuples for fixed records where position carries meaning, like a coordinate or a React useState pair.

typescript
let tags: string[] = ["ts", "js"];
let entry: [string, number] = ["age", 31];
// entry = [31, "age"];   // Error: wrong order

Q12. What are enums in TypeScript?

An enum names a set of related constants: enum Status { Active, Inactive } gives you Status.Active. Numeric enums auto-number from 0; string enums assign explicit string values, which read better in logs and storage.

Enums exist at runtime as real objects, unlike most TypeScript types. Many teams prefer a union of string literals or an as-const object instead, because those add nothing to the compiled output. Knowing that trade-off is worth mentioning.

typescript
enum Status { Active = "active", Inactive = "inactive" }
let s: Status = Status.Active;

// lighter alternative, no runtime object:
type StatusLite = "active" | "inactive";

Q13. How do you type functions in TypeScript?

You annotate each parameter and, when it isn't obvious, the return type: function add(a: number, b: number): number. Optional parameters get a ?, and you can give defaults. The compiler checks callers pass the right types and use the return correctly.

You can also type a function as a value with a function-type signature, which is how you type callbacks and higher-order functions.

typescript
function add(a: number, b: number): number {
  return a + b;
}

// function as a value / callback type
const apply: (fn: (n: number) => number, x: number) => number =
  (fn, x) => fn(x);

Q14. How do optional properties and optional parameters work?

A ? after a property name or parameter makes it optional: { name: string; nickname?: string } means nickname may be missing. The compiler then types it as string | undefined, so you must handle the undefined case before using it.

Optional parameters must come after required ones. Default values are related but different: a default supplies a value when the argument is missing, so the parameter isn't undefined inside the function.

typescript
function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name;
}
greet("Asha");            // OK
greet("Asha", "Dr.");     // OK

Q15. What is a type assertion and when should you use one?

A type assertion tells the compiler to treat a value as a specific type using the as keyword: value as string. It has no runtime effect and does no conversion; it only overrides what the compiler inferred.

Use it sparingly, when you genuinely know more than the compiler (a DOM element you're sure is an input, JSON you've just validated). Overusing it is a way to silence real errors, so an assertion should feel like a rare, deliberate choice.

typescript
const el = document.getElementById("email") as HTMLInputElement;
el.value = "hi@example.com";   // .value exists on HTMLInputElement

Key point: the key signal is the caveat: an assertion doesn't check anything, so a wrong assertion is a runtime crash the compiler let through.

Q16. What is the difference between void and never?

void is the return type of a function that returns nothing useful (a logger, an event handler). never is the type of something that never produces a value at all: a function that always throws or runs forever, or a branch that can't be reached.

The key contrast: void means 'returns, but with no meaningful value'; never means 'does not return'. never is also what an exhaustive switch narrows the default case to, which is how you get compile-time exhaustiveness checks.

typescript
function log(msg: string): void {
  console.log(msg);
}

function fail(msg: string): never {
  throw new Error(msg);
}

Q17. What does the readonly modifier do?

readonly marks a property so it can only be set once, at construction, and never reassigned after. The compiler flags any later write. It's a compile-time guarantee only; nothing stops mutation at runtime.

There's also ReadonlyArray<T> (and readonly T[]) for arrays you don't want mutated, which removes push, pop, and index assignment from the type.

typescript
interface Config {
  readonly apiUrl: string;
}
const c: Config = { apiUrl: "https://api.example.com" };
// c.apiUrl = "...";   // Error: read-only property

Q18. How do you compile TypeScript, and what is tsconfig.json?

You run the compiler, tsc, which reads your .ts files and emits .js. tsconfig.json is the project's config file: it sets the target JavaScript version, module system, strictness flags, output folder, and which files to include.

In real projects a bundler (Vite, esbuild, webpack) or a runtime like ts-node usually handles compilation, but the settings still come from tsconfig.json. Knowing that strict lives here, and turning it on, is the single most impactful config choice.

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist"
  }
}

Q19. What is structural typing (duck typing) in TypeScript?

TypeScript checks type compatibility by shape, not by name. If an object has all the properties a type requires, it's assignable to that type, even if it was never declared to implement it. This is structural typing.

It's why an object literal with the right fields fits an interface without any implements keyword, and why two differently named types with identical shapes are interchangeable. It differs from nominal typing in languages like Java, where the name has to match.

typescript
interface Named { name: string; }
function greet(n: Named) { return `Hi ${n.name}`; }

const dog = { name: "Rex", legs: 4 };
greet(dog);   // OK: dog has the required shape

Q20. What is the difference between object, {}, and unknown as a type?

These three look similar and behave very differently. object means any non-primitive (arrays, functions, objects) but not string or number. The empty type {} means any value that isn't null or undefined, including primitives, which surprises people. unknown means any value at all, but you can't use it until you narrow it.

The takeaway: {} is not 'an empty object', it's 'anything non-nullish', so it's a poor choice when you mean a real object. Reach for object for non-primitives, a specific shape for known data, and unknown when the value is genuinely open.

TypeAcceptsRejects
objectArrays, functions, objectsstring, number, boolean, null, undefined
{}Anything except null and undefinednull, undefined
unknownAny value at allNothing, but you must narrow before use

Key point: Knowing that {} means 'non-nullish anything', not 'empty object', is a favorite gotcha. It catches people who assume the syntax means what it looks like.

Back to question list

TypeScript Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: generics, narrowing, utility types, and the questions that separate users from understanders.

Q21. What are generics and why are they useful?

Generics let you write a function, class, or type that works over many types while keeping the type relationship intact. A type parameter like <T> is a placeholder the caller fills in, so identity<T>(x: T): T returns exactly the type it received.

They replace any where you'd otherwise lose type safety. Without generics, a reusable container or utility either locks to one type or falls back to any and drops all checking. Generics keep both reuse and safety.

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

const n = first([1, 2, 3]);      // number | undefined
const s = first(["a", "b"]);    // string | undefined

Key point: The bar is writing a generic function from memory and explaining that T links the input and output types. The follow-up is usually generic constraints.

Watch a deeper explanation

Video: Typescript Generics Tutorial (Ben Awad, YouTube)

Q22. What are generic constraints?

A constraint limits what a type parameter can be, using extends: <T extends { length: number }> means T must have a length property. Inside the function you can then safely use that property, which a bare T wouldn't allow.

Constraints are how you write generics that do something with the value instead of just passing it through. They keep the function reusable while guaranteeing the members you need exist.

typescript
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
longest([1, 2], [1, 2, 3]);     // OK
longest("ab", "abc");           // OK
// longest(1, 2);               // Error: number has no length

Q23. What is type narrowing and how does control flow analysis work?

Narrowing is the compiler refining a broad type to a more specific one based on the checks you write. Inside if (typeof x === "string"), the compiler knows x is a string in that block. This is control flow analysis: it tracks what must be true along each path.

The everyday narrowing tools are typeof for primitives, instanceof for classes, the in operator for properties, truthiness checks, and equality with a discriminant. Good narrowing is what lets you use unions safely without assertions.

typescript
function area(shape: { size: number } | { radius: number }): number {
  if ("radius" in shape) return Math.PI * shape.radius ** 2;
  return shape.size ** 2;   // narrowed to { size: number }
}

Q24. What is a user-defined type guard?

A type guard is a function whose return type is a type predicate, value is Type. When it returns true, the compiler narrows the argument to that type for the rest of the block. It's how you package a reusable narrowing check.

Use it when the built-in checks (typeof, instanceof, in) aren't enough, for example validating that an unknown value matches a specific interface before you trust it.

typescript
interface Cat { meow: () => void }

function isCat(a: unknown): a is Cat {
  return typeof a === "object" && a !== null && "meow" in a;
}

function speak(a: unknown) {
  if (isCat(a)) a.meow();   // narrowed to Cat
}

Key point: The a is Cat return type is the whole answer. the question needs to see you know a boolean return alone won't narrow anything.

Q25. What is a discriminated union and why is it useful?

A discriminated union is a union of object types that all share a common literal field, the discriminant, like kind: "circle" versus kind: "square". Switching on that field narrows to the exact member, so each branch has the right properties.

It's the go-to pattern for modeling state that can be one of several shapes: a request that's loading, loaded, or errored, or a shape that's a circle or a rectangle. Paired with a never default, it gives compile-time exhaustiveness.

typescript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
  }
}

Q26. Which built-in utility types should you know?

The everyday set: Partial<T> (all properties optional), Required<T> (all required), Readonly<T> (all readonly), Pick<T, K> (keep some keys), Omit<T, K> (drop some keys), Record<K, V> (build a map type), and Exclude / Extract for filtering unions.

They save you from hand-writing derived shapes and keep them in sync with the source type. If User changes, Partial<User> and Omit<User, "id"> change with it, which is the whole point.

UtilityWhat it produces
Partial<T>All properties optional
Required<T>All properties required
Readonly<T>All properties readonly
Pick<T, K>Only the listed keys of T
Omit<T, K>T without the listed keys
Record<K, V>An object type mapping keys K to values V

Key point: Naming five utility types and what each does, without pausing, indicates real day-to-day TypeScript experience.

Q27. What is the difference between Pick and Omit?

Both derive a new object type from an existing one by selecting keys. Pick<T, K> keeps only the keys you list; Omit<T, K> keeps everything except the keys you list. They're opposites, and you choose whichever names fewer keys.

They're common at boundaries: a create-user payload might Omit the server-generated id, and a summary view might Pick just the display fields.

typescript
interface User { id: string; name: string; email: string; }

type PublicUser = Omit<User, "email">;      // id, name
type UserPreview = Pick<User, "id" | "name">; // id, name

Q28. What does the keyof operator do?

keyof takes an object type and produces a union of its property names as literal types. keyof { a: 1; b: 2 } is "a" | "b". It turns the shape of a type into a set of valid keys you can constrain against.

It's the foundation for typed property access: a function that reads a key off an object can constrain the key parameter to keyof T, so the compiler rejects any key the object doesn't have.

typescript
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const user = { name: "Asha", age: 31 };
getProp(user, "name");   // string
// getProp(user, "foo"); // Error: not a key

Q29. What does the typeof type operator do in TypeScript?

In a type position, typeof takes a value and gives you its type. typeof config produces the type the compiler inferred for the config object, so you can reuse it without re-declaring the shape by hand.

This is different from JavaScript's runtime typeof, which returns a string. The type-level typeof is how you derive types from existing values, often combined with keyof and as const.

typescript
const settings = { theme: "dark", retries: 3 };
type Settings = typeof settings;
// Settings is { theme: string; retries: number }

Q30. What are mapped types?

A mapped type builds a new type by transforming each property of an existing one, using the [K in keyof T] syntax. It's how the built-in utilities like Partial and Readonly are defined: iterate the keys, apply a modifier or a new value type.

You reach for a custom mapped type when the built-ins don't fit, for example making every property nullable, or turning a type of values into a type of getters.

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

interface User { name: string; age: number; }
type MaybeUser = Nullable<User>;
// { name: string | null; age: number | null }

Q31. What are conditional types?

A conditional type chooses between two types based on a relationship, using the T extends U ? X : Y form. It's a type-level if statement: if T is assignable to U, the type is X, otherwise Y.

They power the filtering utilities (Exclude, Extract, NonNullable) and let library authors compute a return type from an argument type. With infer inside the condition, you can also extract a piece of a type, like the element type of an array.

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

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

Q32. What is an index signature?

An index signature types an object whose keys you don't know in advance: { [key: string]: number } describes an object with any number of string keys, all mapping to numbers. It's how you type dictionaries and lookup maps.

The trade-off is that every access is typed as the value type, so the compiler can't warn you about a missing key. For a fixed set of keys, Record or a plain interface is safer; use an index signature only when keys are genuinely open-ended.

typescript
interface Scores {
  [player: string]: number;
}
const s: Scores = { asha: 91, ben: 84 };
s["cara"] = 77;   // any string key is allowed

Q33. What does strictNullChecks do?

With strictNullChecks on, null and undefined are their own types and aren't assignable to other types unless you say so. A string can't secretly be null; if it might be, its type is string | null and the compiler forces you to handle the null case.

This is one of TypeScript's biggest bug-catching wins: it eliminates a whole class of 'cannot read property of undefined' crashes by making you narrow before you access. It's part of the strict flag family, and turning it on is worth mentioning as a first move in any project.

typescript
function firstChar(s: string | null): string {
  // return s[0];        // Error: s might be null
  if (s === null) return "";
  return s[0];           // narrowed to string
}

Key point: Interviewers use this to check whether you actually run TypeScript in strict mode. 'I always enable strict' is the answer that lands.

Q34. What are optional chaining and nullish coalescing?

Optional chaining (?.) short-circuits a property access, method call, or index when the value before it is null or undefined, returning undefined instead of throwing. Nullish coalescing (??) supplies a fallback only when the left side is null or undefined.

The pair reads much cleaner than nested null checks. The subtlety with ?? is that it treats 0 and empty string as valid values, unlike ||, which falls through on any falsy value. That difference is a common interview follow-up.

typescript
const name = user?.profile?.name ?? "Guest";

const count = input ?? 0;    // keeps 0 if input is 0
const wrong = input || 0;    // replaces 0 with 0, but drops a real 0 elsewhere

Q35. How do you type classes, and what do access modifiers do?

You annotate class fields and methods like anywhere else, and TypeScript adds access modifiers: public (default), private (only inside the class), and protected (the class and subclasses). private is compile-time; the runtime # private fields enforce it at runtime too.

TypeScript also has a parameter-property shorthand: mark a constructor parameter public or private and it becomes a field automatically, which cuts boilerplate.

typescript
class Account {
  constructor(private balance: number) {}   // becomes a private field
  deposit(amount: number): void {
    this.balance += amount;
  }
}
const a = new Account(100);
// a.balance;   // Error: private

Q36. How does a class implement an interface?

A class uses implements to promise it satisfies an interface's shape: class Repo implements Storage. The compiler then checks the class has every member the interface requires, with matching types.

This is a compile-time contract only; because TypeScript is structurally typed, code elsewhere accepts any object of the right shape whether or not it used implements. So implements is mainly a safety net that catches a missing method early, at the class definition.

typescript
interface Logger {
  log(msg: string): void;
}
class ConsoleLogger implements Logger {
  log(msg: string): void {
    console.log(msg);
  }
}

Q37. What are abstract classes and how do they differ from interfaces?

An abstract class can't be instantiated directly; it's a base that defines some concrete methods and some abstract ones that subclasses must implement. Unlike an interface, it can carry implementation and state, not just a shape.

The rule of thumb: an interface describes a contract with no code, and a type can too; an abstract class is for sharing real implementation across subclasses while still forcing them to fill in specific methods.

typescript
abstract class Shape {
  abstract area(): number;          // subclasses must implement
  describe(): string {              // shared implementation
    return `area is ${this.area()}`;
  }
}
class Circle extends Shape {
  constructor(private r: number) { super(); }
  area(): number { return Math.PI * this.r ** 2; }
}

Q38. How do modules and imports work in TypeScript?

TypeScript uses ES module syntax: export what a file exposes, import it elsewhere. A file with any import or export is a module with its own scope; a file with none is treated as a global script. Named exports and a default export both work, with named exports preferred for refactor-friendly tooling.

TypeScript adds import type for imports used only as types, which the compiler can drop from the output entirely. Knowing that keeps type-only imports from accidentally causing runtime side effects.

typescript
// user.ts
export interface User { name: string; }
export function makeUser(name: string): User { return { name }; }

// main.ts
import { makeUser, type User } from "./user";

Q39. What are declaration files (.d.ts)?

A declaration file describes the types of code without any implementation, using .d.ts. It's how you add TypeScript types to a plain JavaScript library, or how a compiled library ships types alongside its .js so consumers get checking and autocomplete.

For popular JavaScript libraries without built-in types, the community DefinitelyTyped project publishes @types packages you install. When you build a library, the compiler can emit .d.ts files automatically with the declaration flag.

typescript
// legacy.d.ts
declare module "legacy-lib" {
  export function greet(name: string): string;
}

Q40. How do you set up a new TypeScript project from scratch?

You install TypeScript, generate a tsconfig, turn on strict mode, and wire up a build or run step. In practice most people scaffold with a tool (Vite, a framework CLI) that does this, but being able to describe the manual steps shows you understand what those tools automate.

The step that matters most is strict: true in tsconfig, because it turns on the checks that make TypeScript worth using. Skipping it leaves you with typed JavaScript that still lets nulls and implicit anys through.

Setting up a TypeScript project

1Install
npm install --save-dev typescript
2Init config
npx tsc --init, then set target, module, and strict: true
3Write and type
add .ts files, let inference cover locals, annotate boundaries
4Build or run
npx tsc to emit JavaScript, or use ts-node / a bundler in dev

A framework or Vite template does these steps for you. Knowing what each one is means you can debug when the template's defaults don't fit.

Back to question list

TypeScript Interview Questions for Experienced Developers

Experienced20 questions

advanced rounds probe the type system's depth, design judgment, and production scars. Expect every answer here to draw a follow-up.

Q41. Why can't you rely on TypeScript types at runtime?

Types are erased during compilation: the emitted JavaScript has no interfaces, no generics, no type annotations. So you can't check typeof someInterface, and you can't branch on a generic parameter at runtime, because none of that exists once the code runs.

The practical consequence: anything crossing a runtime boundary (API responses, user input, JSON) needs real runtime validation, with a library like Zod or a hand-written guard, not just a type annotation. Types describe what should be true; they don't enforce it against the outside world.

Key point: This is the question that separates people who understand TypeScript from people who think types are runtime checks. Lead with 'types are erased' and the rest follows.

Watch a deeper explanation

Video: TypeScript Tutorial for Beginners (Programming with Mosh, YouTube)

Q42. What is variance, and how does TypeScript handle covariance and contravariance?

Variance is how subtyping on parts relates to subtyping on the whole. TypeScript is covariant on most positions (an array of Dog is usable where an array of Animal is expected) and contravariant on function parameters under strict function types (a function taking Animal is assignable where one taking Dog is expected, because it handles more).

The practical gotcha is that arrays and method parameters are treated bivariantly for convenience, which is technically unsound. Naming that trade-off, and that strictFunctionTypes tightens plain function-type parameters, is the senior-level answer.

Q43. How does the infer keyword work in conditional types?

infer declares a type variable inside a conditional type's extends clause and captures a piece of the type being matched. T extends Promise<infer U> ? U : T pulls the resolved type out of a Promise. It's pattern matching on types.

It's the mechanism behind built-ins like ReturnType and Parameters, and behind any library that derives a type from another. Being able to write ReturnType from scratch with infer is a common production signal.

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

function load() { return { ok: true }; }
type Loaded = MyReturnType<typeof load>;   // { ok: boolean }

Q44. What are template literal types?

Template literal types build string literal types by interpolating other types, using template-string syntax at the type level: type Event = `on${Capitalize<string>}`. They let you compute precise string types instead of falling back to plain string.

They shine for typed string patterns: route paths, CSS units, event names, or a builder that turns keys into getter names. Combined with mapped types and key remapping, they enable surprisingly exact APIs.

typescript
type Color = "red" | "blue";
type Shade = "light" | "dark";
type Swatch = `${Shade}-${Color}`;
// "light-red" | "light-blue" | "dark-red" | "dark-blue"

Q45. What does the as const assertion do?

as const tells the compiler to infer the narrowest, most literal, deeply readonly type for a value. An array becomes a readonly tuple of literals instead of a widened mutable array, and object properties become readonly literal types.

It's the lightweight alternative to enums and the way to derive a union from a value: as const on an array of strings, then typeof arr[number], gives you the string-literal union with zero runtime cost.

typescript
const roles = ["admin", "editor", "viewer"] as const;
type Role = typeof roles[number];
// "admin" | "editor" | "viewer"

Q46. How do you get exhaustiveness checking with never?

You add a default branch that assigns the value to a never variable. If you've handled every member of a discriminated union, the value is narrowed to never there and it compiles. Add a new member without handling it, and the assignment fails, so the compiler points you at the missing case.

This turns 'I forgot to handle the new status' from a runtime bug into a compile error, which is one of the strongest reasons to model state as discriminated unions.

typescript
type Shape = { kind: "circle" } | { kind: "square" };

function handle(s: Shape) {
  switch (s.kind) {
    case "circle": return 1;
    case "square": return 2;
    default:
      const _exhaustive: never = s;   // errors if a case is unhandled
      return _exhaustive;
  }
}

Key point: The never-assignment trick is the answer interviewers hope for. It shows you use the type system to enforce coverage, not just to annotate.

Q47. What are decorators in TypeScript?

A decorator is a function attached with @ to a class, method, accessor, property, or parameter, letting you observe or modify it at definition time. Frameworks like Angular and NestJS use them heavily for dependency injection, routing, and validation metadata.

The honest note: decorators went through an experimental phase (the experimentalDecorators flag) and the standardized version landed later, with some behavioral differences. Knowing there are two decorator eras, and which one a codebase uses, is what separates surface familiarity from real experience.

typescript
function logged(target: any, key: string, desc: PropertyDescriptor) {
  const original = desc.value;
  desc.value = function (...args: any[]) {
    console.log(`calling ${key}`);
    return original.apply(this, args);
  };
}

class Service {
  @logged
  run() { /* ... */ }
}

Q48. What is declaration merging?

Declaration merging is TypeScript combining multiple declarations with the same name into one. Two interfaces with the same name merge their members; a namespace can merge with a class or function to add static-style members. It's why you can augment an existing interface.

Its main real-world use is module augmentation: adding a property to an existing library's type (extending Express's Request, or Window) from your own code without touching the library. That capability is unique to interfaces, one reason they're not fully replaceable by type aliases.

typescript
// augment a global type from your code
declare global {
  interface Window {
    appConfig: { apiUrl: string };
  }
}
window.appConfig.apiUrl;   // now typed

Q49. How do you validate external data when types are compile-time only?

You add a runtime validation layer at every boundary where data enters from outside your control: API responses, form input, environment variables, message queues. A schema library like Zod, Valibot, or io-ts both validates at runtime and infers the static type, so the type and the check stay in one source of truth.

The anti-pattern to name is asserting the shape with as: response as User tells the compiler to trust malformed data, and the crash surfaces deep in the code instead of at the edge. Validate, then trust.

typescript
import { z } from "zod";

const User = z.object({ name: z.string(), age: z.number() });
type User = z.infer<typeof User>;   // { name: string; age: number }

const user = User.parse(await res.json());   // throws on bad data

Key point: Bringing up schema-based validation in practice signals production experience. Interviewers are checking whether you know types don't guard the network.

Q50. How do you write a type-safe deep property accessor with generics and keyof?

You chain keyof and indexed access types across type parameters, constraining each key to the keys of the value at the previous level. Each parameter narrows the next, so the compiler rejects any invalid path and infers the exact return type.

This is the kind of question that checks whether you can compose the type system's primitives (generics, keyof, indexed access, constraints) into something practical, not just recite them individually.

typescript
function get<T, K1 extends keyof T, K2 extends keyof T[K1]>(
  obj: T, k1: K1, k2: K2
): T[K1][K2] {
  return obj[k1][k2];
}
const data = { user: { name: "Asha" } };
get(data, "user", "name");   // string
// get(data, "user", "foo"); // Error

Q51. Where is TypeScript's type system deliberately unsound?

TypeScript trades some soundness for practicality. Known unsound spots: type assertions and any bypass checking entirely, array bounds aren't checked (arr[10] is typed as the element type even when it's undefined at runtime), bivariant method parameters, and object mutation through aliases can violate a narrowed type.

The senior point is that this is a design choice, not a bug: full soundness would reject too much real JavaScript. Knowing the holes tells you where to add runtime checks and where to distrust the green checkmark.

Q52. What does noUncheckedIndexedAccess do, and why enable it?

It makes indexed access include undefined in the result type. Without it, arr[i] on a number[] is typed number even though an out-of-range index gives undefined at runtime. With it, arr[i] is number | undefined, so the compiler forces you to handle the missing case.

It closes one of the array-bounds soundness holes. It adds friction (more narrowing at access sites), so teams weigh it, but for correctness-sensitive code it catches real bugs the default misses.

typescript
const nums: number[] = [1, 2, 3];
const x = nums[10];   // with the flag: number | undefined
// x.toFixed();       // Error until you narrow

Q53. How would you migrate a large JavaScript codebase to TypeScript?

Incrementally, not all at once. Turn on allowJs so JS and TS coexist, rename files module by module, loose settings, and tighten flags (noImplicitAny, then strict) as you go comes first. Type the boundaries first (public APIs, data models) because that's where types pay off fastest, and lean on @types packages for dependencies.

The failure mode to name is a big-bang rewrite that produces thousands of errors and stalls. Gradual migration with CI enforcing no-new-untyped-code is how real teams do it; the compiler's per-file opt-in exists for exactly this.

Q54. When do you use generics versus function overloads?

Generics express a relationship that holds across many types with one signature: the return type is a function of the input type. Overloads list several distinct concrete signatures for the same function name, which fits when the input-output pairings don't follow a single rule.

Prefer generics when one type variable captures the pattern; reach for overloads when the valid argument combinations are a small fixed set that a generic can't express cleanly. Overloads also read better in editor tooltips for those fixed cases.

typescript
// overloads: distinct concrete signatures
function parse(input: string): object;
function parse(input: Buffer): object;
function parse(input: string | Buffer): object {
  return JSON.parse(input.toString());
}

Q55. What does the satisfies operator do, and how is it different from as?

satisfies checks that a value conforms to a type without widening the value's inferred type. You get validation against the type plus the precise literal inference, which a plain type annotation would flatten and which as would unsafely override.

The difference from as is safety: as forces a type onto a value and can hide errors, while satisfies verifies the value fits and keeps the narrow inferred type. It's the right tool for config objects where you want both the check and the exact keys.

typescript
const config = {
  port: 3000,
  host: "localhost",
} satisfies Record<string, string | number>;

config.port.toFixed();   // still known to be a number, not string | number

Key point: satisfies is recent enough that it, and contrasting it with as, matters. Get the 'validates without widening' framing right.

Q56. How do you type asynchronous code and Promises?

An async function's return type is Promise<T>, where T is the resolved value; you annotate the resolved type and the Promise wrapper is implied. Awaiting a Promise<T> gives you a T. For error handling you type nothing special, because rejections aren't in the type; a Promise doesn't carry its rejection type.

That last point is the interview depth: TypeScript can't type what a Promise rejects with, so error handling relies on runtime checks. Some teams model fallible operations as a result union (a discriminated union of ok and error) to make failure visible in the type.

typescript
async function loadUser(id: string): Promise<User> {
  const res = await fetch(`/users/${id}`);
  return res.json() as Promise<User>;   // validate in real code
}
const user: User = await loadUser("42");

Q57. What are branded (nominal) types and why use them?

A branded type simulates nominal typing on top of TypeScript's structural system by intersecting a base type with a unique tag: type UserId = string & { readonly brand: unique symbol }. The tag has no runtime presence; it just makes two structurally identical types (a UserId and a plain string) incompatible.

You use them to stop mixing up values that share a representation but not a meaning: a UserId and an OrderId are both strings, and branding makes passing one where the other is expected a compile error. It's how you buy nominal safety without a nominal type system.

typescript
type UserId = string & { readonly __brand: "UserId" };

function findUser(id: UserId) { /* ... */ }
const raw = "u_123";
// findUser(raw);                 // Error: string is not UserId
findUser(raw as UserId);          // deliberate, at the boundary

Q58. How do you diagnose and fix slow TypeScript compilation?

Measure first with the compiler's diagnostics (extendedDiagnostics and generateTrace) to find where time goes, usually type checking, not emit. Common culprits: very large union or conditional types that explode combinatorially, deep recursive types, and a project that recompiles everything instead of using project references and incremental builds.

Fixes in order: enable incremental and project references to build in units, simplify or cap the pathological types, and split a monolithic tsconfig. the check is that you measure before optimizing and know the type checker is usually the cost, not the JavaScript emit.

Q59. How does TypeScript compare to Flow, and why did it win adoption?

Both are static type checkers that erase to JavaScript, and both cover similar ground: annotations, generics, unions, inference. TypeScript pulled ahead on ecosystem: broad editor support, the DefinitelyTyped type library, first-class framework integration, and Microsoft's sustained investment, while Flow's tooling and community narrowed.

For The production-ready answer, the honest framing is that the language-feature gap was never the deciding factor; tooling, types-for-dependencies availability, and momentum were. That's a useful lesson about how developer tools actually win.

Q60. Design question: how would you type a reusable API client?

Clarify the surface first (which endpoints, static or dynamic routes, shared error shape), then model it with generics: a request method parameterized on the response type, a Record or template-literal type mapping routes to their payload and response, and runtime validation (a schema per endpoint) so the inferred types actually match what arrives.

Close on the boundary discipline: the client's return types should come from the same schemas that validate at runtime, so there's one source of truth. Structuring the answer surface, generic signature, route-to-type map, runtime validation is what the question is really scoring, more than any single type trick.

typescript
interface Routes {
  "/users": { res: User[] };
  "/user": { res: User };
}
async function api<K extends keyof Routes>(path: K): Promise<Routes[K]["res"]> {
  const r = await fetch(path);
  return r.json();   // validate against a schema in real code
}
const users = await api("/users");   // User[]
Back to question list

Why TypeScript? TypeScript vs JavaScript and Flow

TypeScript wins when a codebase grows past the point where one person holds it all in their head: types document intent, catch whole classes of bugs before runtime, and make large refactors safe. It trades a build step and some upfront annotation effort for that safety, so tiny scripts often don't need it while long-lived applications almost always do. The comparison interviewers care about is TypeScript versus plain JavaScript (the safety-versus-friction trade-off) and, less often, versus Flow, the other JavaScript type checker. Knowing these trade-offs out loud is itself an interview signal: it shows you pick tools on merits, not habit.

TypeScriptJavaScriptFlow
Type checkingStatic, at compile timeNone (dynamic only)Static, at compile time
Runtime costZero (types erased)NoneZero (types erased)
Tooling and editor supportFirst class, wide adoptionLimited without typesNarrower ecosystem
OutputCompiles to JavaScriptRuns directlyStrips types to JavaScript
Best atLarge, long-lived codebasesQuick scripts, small appsType-checking existing JS

How to Prepare for a TypeScript Interview

Prepare in layers, and practice out loud. Most TypeScript rounds move from concept questions to live coding to a type-modeling or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet in the TypeScript Playground; watching the compiler flag your mistakes teaches faster than reading.
  • Practice thinking aloud on small typing problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical TypeScript interview flow

1Recruiter or phone screen
background, motivation, a few concept checks
2Technical concepts
types vs interfaces, generics, narrowing, utility types
3Live coding
write and type a function, or model a data shape under observation
4Design or debugging
trade-offs, reading a compiler error, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: TypeScript Quiz

Ready to test your TypeScript knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which TypeScript topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a TypeScript interview?

They cover the question-answer portion well, but most TypeScript rounds also include live coding: writing and typing a function under observation while explaining your thinking. solving small typing problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Do I need to know JavaScript well before TypeScript?

Yes. TypeScript is a superset, so every JavaScript concept still applies: closures, the event loop, prototypes, async. Interviewers freely mix plain JavaScript questions in, and weak JavaScript shows fast once you leave the type annotations. Shore up JavaScript fundamentals alongside the type system.

How long does it take to prepare for a TypeScript interview?

If you already write TypeScript at work, one to two weeks of an hour a day covers this bank with practice runs. Coming from plain JavaScript, plan three to four weeks and write typed code daily; reading answers without typing them is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, types versus interfaces, generics, narrowing, utility types, and the phrasing takes care of itself.

Is there a way to test my TypeScript knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These TypeScript questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 7 Apr 2026Last updated: 18 Jul 2026
Share: