25 questions · Updated

TypeScript Interview Questions and Answers

TypeScript interview questions check whether you use the type system as a design tool or just sprinkle annotations over JavaScript. Interviewers ask about the difference between type aliases and interfaces, how narrowing works, when to reach for generics, and why any and unknown are not the same. Senior candidates get conditional and mapped types, the infer keyword, variance and declaration files. This page collects 25 TypeScript interview questions and answers, split into beginner, intermediate and advanced levels, plus coding exercises where you implement utility types such as Pick, DeepPartial and a type-safe event emitter. Answers reflect TypeScript 5.x behaviour, including the satisfies operator, const type parameters, the using declaration for resource management and how the native Go-based compiler (TypeScript 7) changes tooling speed without changing the language. Because TypeScript compiles to JavaScript, many interviews mix in JavaScript runtime questions as well, so pair this list with the JavaScript interview questions page. Try to answer each question aloud first, then open it to compare.

Beginner TypeScript interview questions

1.

What is TypeScript and what problem does it solve?

TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. It catches type errors at compile time, powers editor features like autocomplete and safe refactoring, and documents intent in code. Types are erased at compile time, so they add no runtime checks: data from the network still needs runtime validation.

2.

What is the difference between type and interface?

Both describe object shapes and are often interchangeable. Interfaces can be reopened and merged by declaring them again (declaration merging) and are extended with extends. Type aliases can name any type, including unions, tuples, primitives and mapped or conditional types, and are combined with intersections (&). A common convention is interfaces for public object contracts and types for everything else.

typesinterfacesLink to this question
3.

What is the difference between any, unknown and never?

any switches off type checking for a value and spreads unsafety through the code. unknown is the type-safe top type: anything can be assigned to it, but you must narrow it before using it. never is the bottom type for values that cannot exist, such as the return type of a function that always throws, or the leftover type after exhaustive narrowing.

4.

What are union and intersection types?

A union A | B means a value is one of the types, so you can only use members common to all of them until you narrow. An intersection A & B means a value has all members of both types, used to combine object types. Discriminated unions, where each member has a literal tag property, are the idiomatic way to model variants.

5.

How does type inference work?

TypeScript infers types from initialisers, return statements and context, so you rarely need to annotate local variables. const declarations of primitives infer literal types, while let infers the wider type. Annotate function parameters and public API boundaries explicitly, and let inference handle the rest.

6.

What are enums, and what alternatives are preferred?

Enums create named constants and, unlike most TypeScript features, emit runtime code. Numeric enums allow reverse mapping and accept any number, which is a known weakness. Many teams prefer a union of string literals or an as const object instead, which emits no extra code and works with the erasable-syntax-only setting used when running TypeScript directly in Node.js.

const Status = { Active: "active", Banned: "banned" } as const;
type Status = (typeof Status)[keyof typeof Status]; // "active" | "banned"
7.

What does strict mode enable?

The strict flag turns on a family of checks, including strictNullChecks (null and undefined are not assignable to other types), noImplicitAny, strictFunctionTypes, strictPropertyInitialization and useUnknownInCatchVariables. New projects should always enable it. noUncheckedIndexedAccess is not part of strict but is worth adding, since it types array and record lookups as possibly undefined.

Intermediate TypeScript interview questions

8.

What are generics?

Generics let you write functions, classes and types that work over many types while preserving the relationship between inputs and outputs. Constraints with extends limit what a type parameter can be. Without generics you would either lose type information with any or duplicate code for each type.

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const n = getProp({ id: 1, name: "a" }, "id"); // number
9.

What is type narrowing?

Narrowing refines a union to a more specific type based on control flow. TypeScript narrows on typeof, instanceof, the in operator, equality checks, truthiness and discriminant property checks. User-defined type guards (value is Type) and assertion functions (asserts value is Type) extend narrowing to custom checks.

type Shape = { kind: "circle"; r: number } | { kind: "square"; size: number };
function area(s: Shape) {
  switch (s.kind) {
    case "circle": return Math.PI * s.r ** 2;
    case "square": return s.size ** 2;
    default: { const _exhaustive: never = s; return _exhaustive; }
  }
}
narrowingunionsLink to this question
10.

Which built-in utility types should you know?

Partial and Required change optionality; Readonly makes properties read-only; Pick and Omit select or remove keys; Record<K, V> builds an object type from keys; Exclude and Extract filter unions; NonNullable removes null and undefined; ReturnType, Parameters and Awaited extract types from functions and promises. Knowing how a few are implemented, such as Pick and Partial, is a common follow-up.

11.

What does the satisfies operator do?

satisfies checks that an expression matches a type without changing the expression's inferred type. With an annotation, the variable is widened to the annotated type and you lose specific literal information; with satisfies, you get validation and keep the precise type. It is ideal for configuration objects and lookup maps.

const routes = {
  home: "/",
  post: "/posts/:id",
} satisfies Record<string, string>;
routes.post;    // OK: keys are preserved
// routes.posts; // compile error, unlike with a Record<string, string> annotation
12.

What is the difference between as const and a regular declaration?

as const tells the compiler to infer the narrowest possible type: literal types instead of string or number, readonly properties and readonly tuples instead of arrays. It is how you derive union types from runtime arrays or objects without repeating values.

const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number]; // "admin" | "editor" | "viewer"
13.

What are keyof and typeof in type positions?

keyof T produces a union of T's property names. typeof in a type position gets the type of a value, such as typeof config. Combined with indexed access types (T[K]), they let you derive types from existing values and keep runtime data and types in sync.

type operatorsLink to this question
14.

What are type assertions and why are they risky?

An assertion such as value as User tells the compiler to treat a value as a type without any runtime check. If you are wrong, errors appear later at runtime. The non-null assertion (value!) has the same risk. Prefer narrowing, type guards or runtime validation with a schema library, and reserve assertions for cases where you genuinely know more than the compiler.

assertionssafetyLink to this question
15.

How do you type API responses safely?

Types disappear at runtime, so annotating fetch results with an interface only tells the compiler to trust the server. Validate untrusted data with a schema library such as Zod, Valibot or ArkType and infer the static type from the schema, so one definition gives both runtime checks and types. Treat the parsed result as unknown until validated.

validationsafetyLink to this question

Advanced TypeScript interview questions

16.

What are conditional types and the infer keyword?

A conditional type T extends U ? X : Y picks a type based on assignability. When T is a naked type parameter and a union is passed in, the condition distributes over each member. infer declares a type variable inside the extends clause to capture part of a type, which is how ReturnType and Awaited are built.

type ElementType<T> = T extends readonly (infer E)[] ? E : never;
type A = ElementType<string[]>; // string
conditional typesinferLink to this question
17.

What are mapped types and key remapping?

Mapped types iterate over keys to build new object types, for example { [K in keyof T]: T[K] | null }. Modifiers can add or remove readonly and optional with + and -. The as clause remaps keys, which combined with template literal types can generate names like getName from name, or filter keys by returning never.

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type G = Getters<{ name: string }>; // { getName: () => string }
mapped typestemplate literal typesLink to this question
18.

What is structural typing, and how do you get nominal (branded) types?

TypeScript compares types by shape, so two types with the same structure are interchangeable even if they have different names. That makes it easy to pass a UserId where an OrderId is expected if both are strings. Branded types intersect a primitive with a unique marker property so they are no longer interchangeable, with a constructor function as the only way to create them.

type UserId = string & { readonly __brand: "UserId" };
const toUserId = (s: string) => s as UserId;
structural typingbranded typesLink to this question
19.

What are declaration files and module augmentation?

Declaration files (.d.ts) describe the types of JavaScript code without implementations; libraries ship them or get them from DefinitelyTyped (@types packages). Module augmentation (declare module "lib" { ... }) and global augmentation (declare global) let you add types to existing modules, for example custom properties on a framework's request object.

declarationsmodulesLink to this question
20.

What are const type parameters and the using declaration?

A const modifier on a type parameter (function f<const T>(x: T)) makes inference treat arguments as if written with as const, preserving literal types without the caller adding it. The using and await using declarations (TypeScript 5.2) call a resource's Symbol.dispose or Symbol.asyncDispose method automatically when it goes out of scope, similar to try/finally cleanup.

genericsmodern tsLink to this question

TypeScript coding exercises

21.

Implement your own Pick and Partial.

Both are mapped types over keys. Pick restricts the keys to a subset constrained by keyof T; Partial adds the optional modifier to every key. Writing them from memory shows you understand mapped types.

type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyPartial<T> = { [P in keyof T]?: T[P] };
utility typescodingLink to this question
22.

Write a DeepPartial type.

Recurse into properties that are objects and make every level optional. Exclude functions so methods are not mangled; arrays are often handled separately depending on requirements.

type DeepPartial<T> = T extends (...args: any[]) => any
  ? T
  : T extends object
    ? { [K in keyof T]?: DeepPartial<T[K]> }
    : T;
recursive typescodingLink to this question
23.

Build a type-safe event emitter.

Parameterise the emitter with a map from event names to payload types. on and emit then use keyof to restrict names and indexed access to type the payload, so emitting the wrong payload or a misspelled event is a compile error.

class Emitter<Events extends Record<string, unknown>> {
  private handlers: { [E in keyof Events]?: ((p: Events[E]) => void)[] } = {};
  on<E extends keyof Events>(event: E, fn: (p: Events[E]) => void) {
    (this.handlers[event] ??= []).push(fn);
  }
  emit<E extends keyof Events>(event: E, payload: Events[E]) {
    this.handlers[event]?.forEach((fn) => fn(payload));
  }
}
const bus = new Emitter<{ login: { userId: string }; logout: undefined }>();
bus.emit("login", { userId: "42" });
genericscodingLink to this question
24.

Type a function that groups an array by a key.

Use a generic for the item type and a key constrained to keys whose values can be property keys. The return type is a partial record because not every possible key will be present.

function groupBy<T, K extends PropertyKey>(items: T[], keyFn: (item: T) => K) {
  const out: Partial<Record<K, T[]>> = {};
  for (const item of items) (out[keyFn(item)] ??= []).push(item);
  return out;
}
genericscodingLink to this question
25.

Make an exhaustive switch over a union fail to compile when a case is missing.

Assign the narrowed value to a variable of type never in the default branch. If someone adds a new union member without a case, the value is no longer never and the assignment errors at compile time.

function assertNever(x: never): never {
  throw new Error("Unexpected value: " + JSON.stringify(x));
}
narrowingcodingLink to this question

Preparing for a TypeScript interview

Common questions about getting ready

How do I prepare for a TypeScript interview?

Make sure you can explain types versus interfaces, unknown versus any, narrowing, generics with constraints and the common utility types. Then practise writing a few utility types from scratch and typing real functions such as groupBy or a fetch wrapper. Most interviews also include JavaScript runtime questions.

Do TypeScript interviews include JavaScript questions?

Almost always. TypeScript only adds compile-time types, so closures, the event loop, promises and this behave exactly as in JavaScript. Review the JavaScript interview questions alongside this list.

How advanced do type-level questions get?

For application roles, conditional types, mapped types and infer are the usual ceiling. Library and platform roles may ask for recursive types, variance or type-level string manipulation. Clear, readable types matter more than clever ones.

Does the new native TypeScript compiler change what I need to know?

Not for the language itself. The Go-based native compiler targets much faster type-checking and editor performance with the same type system semantics, so your knowledge carries over. It is worth knowing it exists if asked about tooling.

Related resources

More interview questions