TypeScript Interview Questions 2026

If you're prepping for a frontend, backend, or full stack role right now, odds are TypeScript is going to come up somewhere in the process, even if the job description just says "JavaScript." Most React, Node, Angular, and NestJS codebases are written in TypeScript by default these days, so interviewers assume you've used it, not just heard of it.

This page is a working list of TypeScript questions that actually get asked, from basic type annotations to the trickier stuff around generics, narrowing, and the utility types people always forget the names of. A lot of these come up naturally in "explain this code" style rounds rather than pure Q&A, so try to actually run the snippets below instead of just reading them.

Why Interviewers Care About TypeScript

TypeScript doesn't change what JavaScript can do at runtime, it just adds a type layer that gets checked at compile time and then stripped away. So when someone asks you TypeScript questions, they're usually not testing "do you know TypeScript syntax," they're testing whether you understand:

  • How to model data shapes so bugs get caught before the code ever runs
  • When any is a shortcut you're taking and when it's actually hiding a real problem
  • How generics let you write one function instead of five near-identical ones
  • Why the compiler is complaining about something that "obviously works"

Who This Page Is For

  • Frontend and full stack developers interviewing for React, Angular, or Next.js roles
  • Backend developers working with Node.js and NestJS
  • Anyone who's used TypeScript casually but never had to explain why it works the way it does

TypeScript Interview Questions and Answers (2026)

TypeScript Basics

1. What is TypeScript, and what problem does it actually solve for you?

TypeScript is JavaScript with a type system bolted on top. It compiles down to plain JavaScript, so at runtime there's no difference, the types don't exist anymore by the time your code is running in a browser or Node. The real value is at development time: the compiler catches a whole category of bugs (passing a string where you meant a number, calling a method on something that might be undefined) before you ever hit save and run the app.

2. If TypeScript just gets erased at compile time, why not just use JSDoc comments in plain JavaScript instead?

You technically can, and some smaller projects do exactly that. But JSDoc gets verbose fast and doesn't give you nearly as good tooling support, especially for generics, discriminated unions, or complex inferred types. TypeScript's type syntax is also just easier to read once you're used to it, and refactoring tools (rename symbol, find all references) work far more reliably with real TS types than comment-based ones.

3. What's the difference between any and unknown?

This one gets asked constantly, and it's worth actually knowing cold. any turns off type checking completely for that value, you can call any method, access any property, assign it to anything, and TypeScript won't say a word. unknown also accepts any value, but you can't do anything with it until you narrow it down first (via a type guard, typeof check, or a cast).

let a: any = 'hello';
a.toUpperCase(); // fine, TS won't check this at all
a.thisDoesNotExist(); // also "fine" at compile time, blows up at runtime

let u: unknown = 'hello';
u.toUpperCase(); // Error: Object is of type 'unknown'
if (typeof u === 'string') {
  u.toUpperCase(); // fine now, TS narrowed it to string
}

Basically, unknown is the type-safe version of any. If you're tempted to reach for any, unknown is usually what you actually want.

4. What's the difference between interface and type, and does it actually matter which one you pick?

Most of the time, no, it doesn't matter much and it comes down to team convention. Both can describe the shape of an object. The real differences: interfaces support declaration merging (you can declare the same interface twice and TypeScript merges the fields), and extends on an interface gives slightly clearer error messages than intersecting types. type aliases are more flexible though, they can represent unions, tuples, and primitives, which an interface can't do on its own.

A common rule of thumb: use interface for object shapes that might need to be extended (like public API contracts or component props), and type for unions, tuples, or anything that isn't just "an object with these fields."

5. What does "structural typing" mean in TypeScript, and how is it different from what languages like Java or C# do?

TypeScript checks types based on shape, not name. If two objects have the same properties with compatible types, they're considered the same type as far as TypeScript is concerned, even if they were declared completely separately with different names. Java and C# use nominal typing, where a class only matches a type if it explicitly declares that it implements it. This trips people up coming from a nominally typed background, because in TypeScript you never need an explicit "implements" to satisfy an interface, the shape just has to match.

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

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

const obj = { x: 10, y: 20, z: 30 }; // has an extra field
printPoint(obj); // works fine, obj has everything Point needs

6. What is type inference, and why do experienced TypeScript devs avoid annotating everything?

Type inference is TypeScript figuring out a variable's type on its own based on how it's initialized, without you writing an explicit annotation. If you write const age = 25, TypeScript already knows that's a number, so writing const age: number = 25 is redundant. Experienced devs lean on inference heavily because over-annotating adds noise without adding safety, and it can actually make refactors harder since you now have two places (the annotation and the actual value) that need to stay in sync.


Interfaces, Type Aliases and Object Shapes

1. How do optional properties work, and what's the actual difference between prop?: string and prop: string | undefined?

They look similar but aren't quite the same. prop?: string means the property can be entirely absent from the object. prop: string | undefined means the property must exist, but its value is allowed to be undefined. In practice, with exactOptionalPropertyTypes off (the default), these end up behaving almost identically, but with that flag turned on, TypeScript actually distinguishes "missing key" from "key present with value undefined," which matters for object spreads and destructuring.

2. What are readonly properties, and do they actually prevent mutation at runtime?

No, and this catches people off guard. readonly is purely a compile-time check. It stops you, the developer, from writing code that reassigns the property after the object was created, but it does nothing at runtime, someone can still mutate the object through a type cast, through Object.assign, or just by not going through TypeScript at all (say, a plain JS file importing your module).

interface User {
  readonly id: number;
  name: string;
}

const user: User = { id: 1, name: 'Asha' };
user.id = 2; // Error at compile time
(user as any).id = 2; // works fine, TS gets bypassed

3. How do index signatures work, and when would you actually use one?

An index signature lets you type an object where you don't know the exact property names ahead of time, only the shape of the keys and values. You'd use one for things like a dictionary or a lookup table built from dynamic data, for example a map of country codes to country names coming from an API.

interface CountryMap {
  [countryCode: string]: string;
}

const countries: CountryMap = {
  IN: 'India',
  US: 'United States',
};

4. What's the difference between extending an interface and intersecting two types with &?

Functionally they often end up producing a similar result, a type that has all the members of both. The practical difference shows up when there's a conflict: extending an interface with an incompatible property type gives you a clear compile error right at the extends clause. Intersecting two conflicting types with & doesn't error the same way, it just silently collapses the conflicting property to never, which is a much more confusing thing to debug later.

5. Can you explain what a discriminated union is, with an example?

A discriminated union is a group of object types that all share one common property (the "discriminant") with a different literal value in each variant, which lets TypeScript figure out exactly which variant you're dealing with once you check that one field.

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

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

Inside each case, TypeScript automatically narrows shape to the matching variant, so you get autocomplete for radius in the circle branch and side in the square branch, no manual casting needed. This is one of the more useful patterns in TypeScript once it clicks.


Generics

1. What are generics, and what problem do they actually solve that a regular function can't?

Generics let a function, interface, or class work with a type that's decided at the point of use instead of hardcoded when you write it. Without generics, if you wanted a function that returns the first item of an array while preserving the item's type, you'd either have to write it separately for every type or fall back to any and lose type safety entirely. Generics give you both: reusability and the correct return type.

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

const num = first([1, 2, 3]); // inferred as number | undefined
const str = first(['a', 'b']); // inferred as string | undefined

2. What does T extends U mean when used as a generic constraint?

It restricts what T is allowed to be, T must be assignable to U, not any type at all. This is useful when your function needs to rely on some property or method existing on the input, and you want the compiler to enforce that instead of finding out at runtime.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength('hello'); // fine, strings have .length
getLength([1, 2, 3]); // fine, arrays have .length
getLength(42); // Error, number doesn't have .length

3. What's the difference between a generic constraint (T extends U) and a conditional type (T extends U ? X : Y)?

They use the same keyword but do different jobs. A constraint limits what T is allowed to be. A conditional type is a branch, it picks between two different resulting types (X or Y) depending on whether T matches U. Conditional types are how a lot of TypeScript's built-in utility types are implemented internally.

4. What is generic inference, and can you give an example where TypeScript infers the generic without you writing it explicitly?

Generic inference is TypeScript figuring out what T should be based on the arguments you actually pass, instead of you writing <T> at the call site. Most of the time you never write the type argument explicitly, it's inferred.

function identity<T>(value: T): T {
  return value;
}

identity(42); // T is inferred as number, no need for identity<number>(42)

5. Why would you use multiple type parameters, like <K, V>, instead of one?

Because sometimes a function genuinely deals with two independent, related types that both need to be tracked separately, like a key and a value in a map-like structure. Forcing everything into a single T would either lose information or force you into an awkward tuple type.

function createPair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

const pair = createPair('age', 25); // [string, number]

Advanced and Utility Types

1. What does the Partial<T> utility type do, and where would you actually use it?

Partial<T> takes a type and makes every property optional. It comes up constantly for update functions, where you're patching an existing object and don't want to force the caller to pass every single field, just the ones they're actually changing.

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

function updateUser(id: number, changes: Partial<User>) {
  // changes might only have { name: "new name" }
}

2. What's the difference between Pick<T, K> and Omit<T, K>?

Pick builds a new type by selecting only the listed keys from T. Omit does the opposite, it builds a new type with everything from T except the listed keys. They're essentially inverses of each other, and honestly people mix them up mid-interview all the time, so it's worth just remembering "Pick keeps, Omit removes."

interface User {
  id: number;
  name: string;
  password: string;
}

type PublicUser = Omit<User, 'password'>; // { id, name }
type UserCredentials = Pick<User, 'name' | 'password'>; // { name, password }

3. What does Record<K, V> do, and how is it different from just writing an index signature yourself?

Record<K, V> builds an object type where every key in K maps to a value of type V. It's basically shorthand for a mapped type. The advantage over a raw index signature is that if K is a union of specific string literals, TypeScript will actually enforce that every one of those keys is present, an index signature doesn't give you that guarantee.

type Role = 'admin' | 'editor' | 'viewer';

const permissions: Record<Role, string[]> = {
  admin: ['read', 'write', 'delete'],
  editor: ['read', 'write'],
  viewer: ['read'],
};
// forgetting one of the three keys here is a compile error

4. What's the difference between ReturnType<T> and just typing the return value manually?

ReturnType<T> extracts the return type of a function type automatically, which is genuinely useful when the function's return type is complex or comes from a third-party library and you don't want to duplicate it by hand. It keeps your type in sync with the actual function, if the function's return type changes, your derived type updates automatically instead of silently going stale.

function createUser() {
  return { id: 1, name: 'Asha', createdAt: new Date() };
}

type NewUser = ReturnType<typeof createUser>;

5. What are mapped types, and how would you write one from scratch (without using a built-in utility type)?

A mapped type lets you build a new type by transforming every property of an existing type, using [K in keyof T] syntax. This is literally how Partial, Readonly, and Record are implemented under the hood in TypeScript's standard library.

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

6. What is a template literal type, and what's an actual use case for one?

Template literal types let you build string types out of other types using the same ${} syntax as template literal strings, but at the type level. A common use case is typing event names or CSS-in-JS style keys where the string has a predictable pattern.

type EventName = 'click' | 'hover' | 'focus';
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onHover" | "onFocus"

Type Narrowing and Guards

1. What is type narrowing, and why does the order of your checks sometimes matter?

Narrowing is the process where TypeScript reduces a broad type (like a union) down to a more specific one within a certain block of code, based on runtime checks you write like typeof, instanceof, or truthiness checks. Order matters because narrowing is flow-based, TypeScript tracks the type as it exists at each line, so a check earlier in the function genuinely changes what TypeScript believes about the variable's type in the lines after it.

2. What's a custom type guard, and how do you write one using the is keyword?

A custom type guard is a function that returns a boolean but is annotated to tell TypeScript "if this returns true, treat the argument as this specific type from here on." You write it using arg is SomeType as the return type instead of just boolean.

interface Cat {
  meow(): void;
}
interface Dog {
  bark(): void;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined;
}

function speak(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow(); // TS knows it's a Cat here
  } else {
    animal.bark(); // and a Dog here
  }
}

3. Why doesn't typeof narrowing work for distinguishing between two different object shapes?

Because typeof on any plain object just returns "object", no matter what interface it's supposed to satisfy, it doesn't tell TypeScript anything about which specific object shape you're dealing with. For distinguishing between object types, you need either a discriminated union with a literal field, an instanceof check (if they're classes), or a custom type guard, typeof only really helps with primitives.

4. What is the non-null assertion operator (!), and why do senior developers usually tell you to avoid it?

The ! operator tells TypeScript "trust me, this value is not null or undefined," without actually checking anything at runtime. It suppresses the compiler's complaint but does nothing to protect you if you're wrong, if the value actually is null at runtime, you get a regular runtime crash, just like plain JavaScript would. It's used, but usually as a last resort when you genuinely know more than the compiler can infer (like right after a DOM query you know exists), not as a general way to silence errors.

5. What's the difference between narrowing with in and narrowing with instanceof?

in checks whether a property exists on an object, which is useful for narrowing between object literal types that don't share a common tag field. instanceof checks whether an object was constructed by a specific class, so it only works for class instances, not plain object literals.

interface Bird {
  fly(): void;
}
interface Fish {
  swim(): void;
}

function move(animal: Bird | Fish) {
  if ('fly' in animal) {
    animal.fly();
  } else {
    animal.swim();
  }
}

Classes and OOP in TypeScript

1. What do the public, private, and protected access modifiers actually enforce, and is it real encapsulation?

Not really, not at runtime. Like most of TypeScript's type system, these are compile-time only checks. private and protected stop other TypeScript code from accessing the member where they shouldn't, but the JavaScript that gets emitted has no actual privacy, the property is still just a regular object property you can access from plain JS or by casting to any. If you want real runtime privacy, you'd use JavaScript's native #privateField syntax instead, which TypeScript also supports and does enforce at runtime.

2. What's the shorthand for declaring and assigning constructor properties in one step?

You can put access modifiers directly on constructor parameters, and TypeScript automatically creates and assigns the matching class properties for you, no need to declare the field separately and write this.x = x by hand.

class Point {
  constructor(
    private x: number,
    private y: number
  ) {}
}
// equivalent to declaring x and y as private fields
// and assigning them manually in the constructor body

3. What's the difference between an abstract class and an interface, and when would you reach for one over the other?

An interface only describes a shape, it can't contain any actual implementation. An abstract class can contain both fully implemented methods that subclasses inherit as-is, and abstract methods that subclasses are forced to implement themselves. You'd reach for an abstract class when several related classes share real, reusable logic, not just a shape. If there's no shared implementation to reuse, an interface is usually the simpler choice, and a class can implement multiple interfaces but only extend one class.

4. How does TypeScript handle method overriding, and what's the override keyword actually for?

TypeScript lets a subclass redefine a method inherited from its parent, same as JavaScript. The override keyword is an explicit signal to the compiler that you intend to override a parent method, and it will error if the parent class doesn't actually have a matching method, which catches a real bug: renaming a method on the base class without updating a subclass that thought it was overriding it.

5. What are decorators, and are they officially part of standard TypeScript or still experimental?

Decorators are functions that let you attach extra behavior or metadata to a class, method, property, or parameter, using the @ syntax you'd see a lot in Angular or NestJS code. For a long time they required an experimentalDecorators flag and followed an older proposal, but TypeScript has since added support for the newer, TC39 Stage 3 decorators standard as well, so depending on your tsconfig setup you might be using either the legacy or the standard version, and they're not fully interchangeable.


Modules, Enums and Type-Only Imports

1. What's the actual difference between enum and just using a union of string literals?

An enum compiles to a real JavaScript object that exists at runtime, you can iterate over it, log it, pass it around as a value. A union of string literals, like "admin" | "editor" | "viewer", only exists at the type level and gets completely erased at compile time, there's no runtime object backing it at all. A lot of TypeScript style guides these days actually recommend avoiding enum in favor of literal unions or as const objects, mainly because regular numeric enums have some surprising behavior (like reverse mapping) that catches people off guard.

2. What's a const enum, and how is it different from a regular enum?

A const enum gets fully inlined at compile time, references to it are replaced directly with their literal values in the emitted JavaScript, and no actual enum object is generated at all. This makes it slightly more efficient, but it comes with real limitations, it doesn't work well with certain bundler/isolated-module setups, which is why some teams avoid it entirely.

3. What does as const do, and why is it useful as an alternative to enums?

as const tells TypeScript to infer the narrowest possible, literal type for a value instead of widening it to a general type, and it also makes array and object properties readonly. It's commonly used to build an object that acts like an enum but is just a plain, erasable JavaScript object at runtime.

const Role = {
  Admin: 'admin',
  Editor: 'editor',
} as const;

type Role = (typeof Role)[keyof typeof Role]; // "admin" | "editor"

4. What's the difference between import type and a regular import?

import type tells the compiler this import is only used for type checking and should be completely stripped from the emitted JavaScript. A regular import might import a value that's actually used at runtime, or it might also just be importing a type, TypeScript can usually figure it out on its own, but being explicit with import type matters more once you're using tools like esbuild or SWC for transpilation, since those tools transpile files in isolation and can't always tell on their own whether an import is type-only.

5. What are declaration files (.d.ts), and why does an npm package sometimes need a separate @types package?

A .d.ts file contains only type information, no actual runtime code, and its job is to describe the shape of JavaScript that already exists, so TypeScript knows how to type-check code that calls into it. Some npm packages ship their own .d.ts files bundled in, but a lot of older or plain-JavaScript packages don't, which is where @types/package-name packages from DefinitelyTyped come in, they're community-maintained type definitions for packages that don't provide their own.


TypeScript with React

1. How do you type a functional component's props?

You define an interface or type for the props and use it as the parameter type, you generally don't need React.FC these days, most style guides have moved away from it because it forced an implicit children prop even when a component didn't accept one, and it made generic components more awkward to write.

interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

function Button({ label, onClick, disabled }: ButtonProps) {
  return <button onClick={onClick} disabled={disabled}>{label}</button>;
}

2. How do you type the useState hook when the initial value doesn't fully describe the type you actually need?

If you initialize useState with null or undefined but the value will eventually hold a specific shape, TypeScript infers the type as just null, which won't let you assign anything else later. You pass the type explicitly as a generic argument in that case.

interface User {
  id: number;
  name: string;
}

const [user, setUser] = useState<User | null>(null);

3. How do you correctly type a custom hook's return value?

Same as any function, you type the return value, but if you're returning a tuple (like useState does) instead of an object, you need to be careful that TypeScript doesn't widen it to a plain array, which would lose the fixed order and types of each element.

function useToggle(initial = false): [boolean, () => void] {
  const [value, setValue] = useState(initial);
  const toggle = () => setValue((v) => !v);
  return [value, toggle];
}

4. How do you type an event handler for something like an input's onChange?

React provides typed event interfaces for exactly this, like React.ChangeEvent<HTMLInputElement>, so you get proper autocomplete on event.target.value instead of it being typed as any.

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  console.log(e.target.value);
}

5. How would you type a component that needs to accept generic data, like a reusable List component that renders items of any type?

You make the component itself generic, the same way you'd make a function generic, so each usage of the component locks in its own specific item type instead of falling back to any.

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>;
}

tsconfig, Compiler Behavior and Common Gotchas

1. What does strict mode in tsconfig.json actually turn on?

strict is a shortcut flag that enables a whole group of stricter checks at once, including strictNullChecks, noImplicitAny, strictFunctionTypes, and a handful of others. Most teams turn it on for every new project, since disabling it later, once a codebase has grown, means dealing with a wave of errors all at once instead of catching them incrementally.

2. What does strictNullChecks specifically change about how null and undefined behave?

Without it, null and undefined are quietly assignable to basically every type, which defeats a lot of the point of having a type system in the first place, since almost any value could secretly be null. With it turned on, null and undefined are only assignable to variables that explicitly include them in their type, which forces you to actually handle the null case instead of it silently slipping through.

3. Why can two structurally identical types sometimes behave differently when it comes to excess properties, and what is "excess property checking"?

TypeScript normally allows extra properties on an object as long as the required ones are present, because of structural typing. But when you pass an object literal directly into a function or assign it directly to a typed variable, TypeScript does an extra, stricter check and flags properties that don't exist on the target type. Assigning through an intermediate variable first skips this stricter check, which is a common source of "why did this error go away when I refactored" confusion.

interface Config {
  timeout: number;
}

function setup(config: Config) {}

setup({ timeout: 100, retries: 3 }); // Error, excess property "retries"

const obj = { timeout: 100, retries: 3 };
setup(obj); // no error, structural typing applies here instead

4. Why would TypeScript compile without errors but the code still crash at runtime?

Because the type system only ever reasons about the types you've told it about, or that it can infer, it has no idea what's actually happening at runtime, especially with data coming from outside your program, an API response, JSON.parse, user input, or a third-party library with loose or incorrect type definitions. If you type an API response as a specific interface without actually validating the response shape at runtime, TypeScript will happily let you access fields that might not exist, and you'll only find out when it crashes in production. This is usually the argument for pairing TypeScript with a runtime validation library like Zod for anything crossing a real boundary.

5. What's the difference between tsc's type checking and what a bundler like esbuild, SWC, or Babel does with TypeScript files?

tsc, the official TypeScript compiler, does full type checking and then emits JavaScript. Fast bundlers like esbuild and SWC strip TypeScript syntax and transpile to JavaScript without actually checking types at all, they just remove the annotations, they're basically treating your .ts file as JavaScript with extra syntax to delete. That's why a lot of modern setups run tsc --noEmit separately in CI just for type checking, while the actual build/bundle step is handled by a faster tool that skips type checking entirely.



Struggling to Find a Job? Get Specific Batch Wise job Updates ✅ Check now

Join our WhatsApp Channel for more resources.