Interview Q&A · 100 questions · 20 answered publicly
TypeScript Developer Interview Questions
Ordered by how often they're asked. Sign in free to see every answer, rate your confidence and run timed mock interviews.
-
Most asked What is TypeScript and what problems does it solve?
Summary
TypeScript is a statically typed superset of JavaScript that adds a type system and compiles (by erasing types) to plain JavaScript. It catches type errors at compile time, improves editor tooling and documents intent in large codebases.
Longer Answer
Any valid JavaScript is (almost always) valid TypeScript. The type checker (
tsc) analyses code without running it; types are erased in the output, so there's no runtime type checking. Benefits include safer refactors, autocompletion, self-documenting APIs and catchingundefined is not a functionbugs before production. Tools like esbuild, SWC and Node's type stripping can remove types without checking them, so teams runtsc --noEmitseparately in CI.Key Points
- Superset of JavaScript
- Static checking, types erased at compile time
- No runtime type validation
- Better tooling and refactoring
-
Most asked Explain `any`, `unknown` and `never`.
Summary
anydisables type checking for a value;unknownis the type-safe top type: anything can be assigned to it, but you must narrow it before use;neveris the bottom type for values that never occur (functions that throw, exhaustive checks).Longer Answer
Prefer
unknownfor untrusted input (JSON,catchvariables withuseUnknownInCatchVariables) and narrow withtypeof,instanceofor schema validation.anyspreads silently through code and should be rare (lint withno-explicit-any).neveris useful in exhaustiveswitchstatements: assigning the remaining value tonevermakes the compiler error when a new union member is added.Code Example
function parse(json: string): unknown { return JSON.parse(json); } const data = parse('{"name":"Ana"}'); // data.name; // Error: 'data' is of type 'unknown' if (typeof data === "object" && data !== null && "name" in data) { console.log((data as { name: string }).name); } function assertNever(x: never): never { throw new Error(`Unexpected: ${JSON.stringify(x)}`); }Key Points
any: opt out of checking (contagious)unknown: must narrow before usenever: impossible values, exhaustiveness- Prefer
unknownfor external data
Comparison / Tradeoffs
Type Assign anything to it? Use without checks? any Yes Yes (unsafe) unknown Yes No, narrow first never No N/A -
Most asked What are the differences between `interface` and `type` aliases?
Summary
Both can describe object shapes and be extended. Interfaces support declaration merging and
extends, which reads well for object/class contracts; type aliases can name any type, including unions, tuples, primitives, mapped and conditional types.Longer Answer
Interfaces with the same name in the same scope merge, which is used to augment library types (e.g., adding fields to
Windowor Express'sRequest). Type aliases can't be reopened. For object shapes, either is fine; many teams useinterfacefor public object contracts andtypefor unions and computed types. Interfaceextendserrors on conflicting properties, while intersections (&) may silently produceneverproperties.Code Example
interface User { id: string; name: string } interface User { email: string } // merged: { id, name, email } type Status = "active" | "banned"; // only possible with type alias type WithTimestamps<T> = T & { createdAt: Date; updatedAt: Date };Key Points
- Interfaces: declaration merging,
extends - Types: unions, tuples, mapped/conditional types
- Both work for object shapes
- Use merging to augment library types
- Interfaces: declaration merging,
-
Most asked What is structural typing in TypeScript?
Summary
TypeScript checks compatibility by shape (structure), not by declared names: a value is assignable to a type if it has all the required members with compatible types, regardless of which class or interface created it.
Longer Answer
This differs from nominal typing in Java or C#. Two unrelated interfaces with the same members are interchangeable. Excess property checks only apply to fresh object literals. When you need nominal-like behavior (e.g., not mixing
UserIdandOrderIdstrings), use branded types.Code Example
interface Point { x: number; y: number } class Vector { constructor(public x: number, public y: number) {} } const p: Point = new Vector(1, 2); // OK: same shape type UserId = string & { readonly __brand: "UserId" }; type OrderId = string & { readonly __brand: "OrderId" }; declare function getUser(id: UserId): void; // getUser("abc" as OrderId); // Error: brands differKey Points
- Compatibility by shape
- Unrelated types with same shape are interchangeable
- Excess property checks on object literals
- Brands simulate nominal typing
-
Most asked What is type narrowing and what techniques does TypeScript support?
Summary
Narrowing refines a broad type to a more specific one within a code branch, based on control-flow analysis of checks like
typeof,instanceof,in, equality, truthiness, discriminant properties, and user-defined type guards (x is T) or assertion functions (asserts x is T).Longer Answer
The compiler tracks types through
if/switch/early returns. Since TypeScript 5.5, the compiler can infer type predicates from simple functions likex => x !== null, soarray.filter(...)narrows correctly. Assertion functions throw if the condition fails and narrow afterwards.Code Example
type Shape = { kind: "circle"; r: number } | { kind: "rect"; w: number; h: number }; function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.r ** 2; // s narrowed to circle case "rect": return s.w * s.h; } } function isString(x: unknown): x is string { return typeof x === "string"; } const ids = [1, null, 3].filter((x) => x !== null); // number[] (TS 5.5+)Key Points
typeof,instanceof,in, equality- Discriminated unions with
switch - Custom guards:
(x): x is T - Assertion functions:
asserts x is T - TS 5.5 infers type predicates
-
Most asked What are discriminated unions and why are they powerful?
Summary
A discriminated (tagged) union is a union of object types sharing a literal property (the discriminant, e.g.,
typeorstatus). Checking that property narrows to the exact variant, and exhaustiveness checks ensure every case is handled.Longer Answer
They model state machines and API results precisely, making invalid states unrepresentable (e.g.,
{ status: 'success'; data }vs{ status: 'error'; error }instead of optionaldata?anderror?). Combined withnever-based exhaustiveness checks, adding a variant forces updates everywhere.Code Example
type Result<T> = | { status: "loading" } | { status: "success"; data: T } | { status: "error"; error: Error }; function render(r: Result<string[]>) { switch (r.status) { case "loading": return "Loading…"; case "success": return r.data.join(", "); case "error": return r.error.message; default: { const _exhaustive: never = r; return _exhaustive; } } }Key Points
- Shared literal tag field
- Narrowing per variant
- Exhaustiveness via
never - Make invalid states unrepresentable
-
Most asked What are generics and how do constraints work?
Summary
Generics are type parameters (
<T>) that let functions, classes and types work over many types while preserving type relationships. Constraints (<T extends Something>) restrict whatTcan be so you can safely use its members.Longer Answer
Example:
function first<T>(arr: T[]): T | undefinedreturns the same element type it receives.keyofconstraints relate parameters:function get<T, K extends keyof T>(obj: T, key: K): T[K]. Defaults (<T = string>) and inference from arguments make generic APIs ergonomic. Avoid unnecessary generics: ifTis used only once, a plain type is clearer.Code Example
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "Ana" }; const n = getProp(user, "name"); // string // getProp(user, "age"); // Error: '"age"' not in keyof user function longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b; }Key Points
- Type parameters preserve relationships
extendsconstraintsK extends keyof T→T[K]- Inference from arguments
- Don't over-genericize
-
Most asked Which built-in utility types do you use most, and what do they do?
Summary
Common ones:
Partial<T>,Required<T>,Readonly<T>,Pick<T, K>,Omit<T, K>,Record<K, V>,Exclude<U, X>,Extract<U, X>,NonNullable<T>,ReturnType<F>,Parameters<F>,Awaited<T>andNoInfer<T>(5.4).Longer Answer
They're built with mapped and conditional types. Example uses:
Partial<User>for PATCH payloads,Omit<User, 'password'>for public DTOs,Record<Status, string>for exhaustive lookup tables,ReturnType<typeof createStore>to derive types from implementations, andAwaited<ReturnType<typeof fetchUser>>for async results.Code Example
interface User { id: string; name: string; email: string; password: string } type PublicUser = Omit<User, "password">; type UserPatch = Partial<Pick<User, "name" | "email">>; type StatusLabels = Record<"active" | "banned", string>; async function fetchUser(id: string): Promise<PublicUser> { /* ... */ return null!; } type Fetched = Awaited<ReturnType<typeof fetchUser>>; // PublicUserKey Points
- Partial/Required/Readonly modify properties
- Pick/Omit select keys
- Record for maps with known keys
- ReturnType/Parameters/Awaited derive types
-
Most asked What are mapped types? Show how `Partial` or `Readonly` could be implemented.
Summary
Mapped types create new types by iterating over keys:
{ [K in keyof T]: ... }. Modifiers?,readonly,-?and-readonlyadd or remove optionality and readonly-ness, andasclauses remap or filter keys.Longer Answer
Key remapping with template literal types enables things like generating getter names. Filtering keys uses
aswith a conditional resolving tonever.Code Example
type MyPartial<T> = { [K in keyof T]?: T[K] }; type Mutable<T> = { -readonly [K in keyof T]: T[K] }; type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; type UserGetters = Getters<{ name: string; age: number }>; // { getName: () => string; getAge: () => number } type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] };Key Points
[K in keyof T]iteration?/readonlymodifiers and-removalasfor key remapping/filtering
-
Most asked Explain conditional types and the `infer` keyword.
Summary
Conditional types (
T extends U ? X : Y) choose a type based on assignability.inferdeclares a type variable inside theextendsclause to capture part of a type, e.g. a function's return type or a promise's resolved value.Longer Answer
When
Tis a naked type parameter, conditional types distribute over unions:ToArray<string | number>becomesstring[] | number[]. Wrap in brackets ([T] extends [U]) to prevent distribution. Many built-in utilities (ReturnType,Awaited,Exclude) are conditional types.Code Example
type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never; type ElementType<T> = T extends readonly (infer E)[] ? E : T; type UnwrapPromise<T> = T extends Promise<infer V> ? UnwrapPromise<V> : T; type A = MyReturnType<() => number>; // number type B = ElementType<string[]>; // string type C = UnwrapPromise<Promise<Promise<boolean>>>; // booleanKey Points
T extends U ? X : Yinfer Rcaptures sub-types- Distributive over naked unions
[T] extends [U]disables distribution
-
Most asked What does the `satisfies` operator do and how does it differ from a type annotation?
Summary
satisfies(TypeScript 4.9) checks that an expression conforms to a type without changing the expression's inferred type, so you get validation and keep precise literal types. An annotation (const x: T = ...) widens the variable toT.Longer Answer
Typical use: configuration objects where you want to ensure all keys are valid and values have the right shape, but still want autocomplete on the specific keys and literal values.
Code Example
type Route = { path: string; auth: boolean }; const routes = { home: { path: "/", auth: false }, admin: { path: "/admin", auth: true }, } satisfies Record<string, Route>; routes.admin.path; // OK, and key "admin" is known // routes.missing; // Error: property doesn't exist const annotated: Record<string, Route> = routes; annotated.anything; // no error: keys widened to stringKey Points
- Validates without widening
- Keeps literal/specific inferred types
- Great for config and lookup objects
- Annotations widen to the declared type
-
Most asked What does `as const` do?
Summary
as constmakes TypeScript infer the narrowest possible type: literal types instead ofstring/number,readonlyproperties, and readonly tuples instead of arrays.Longer Answer
It's commonly used to derive union types from runtime arrays or objects, giving a single source of truth:
const ROLES = ['admin', 'user'] as const; type Role = typeof ROLES[number];. Combine withsatisfiesfor validated, literal configs.Code Example
const ROLES = ["admin", "editor", "viewer"] as const; type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer" const config = { retries: 3, mode: "strict" } as const; // config.mode = "loose"; // Error: readonlyKey Points
- Literal types instead of widened ones
- Deep
readonly - Readonly tuples
- Derive unions from runtime values
-
Most asked Should you use enums or union types in TypeScript?
Summary
Many teams prefer string literal unions (optionally derived from an
as constobject) over enums: unions are erased at compile time, work with structural typing and plain strings, and are supported by type-stripping tools. Enums generate runtime objects and have quirks (numeric enums accept any number in older versions;const enumbreaks with isolated modules).Longer Answer
Enums are still fine for some codebases, especially string enums with explicit values. Node's built-in type stripping and
erasableSyntaxOnly(TS 5.8) reject enums because they aren't erasable syntax, which pushes modern code toward unions oras constobjects.Code Example
// Preferred alternative to enum const Status = { Active: "active", Banned: "banned" } as const; type Status = (typeof Status)[keyof typeof Status]; // "active" | "banned" function setStatus(s: Status) {} setStatus(Status.Active); setStatus("banned");Key Points
- Unions: zero runtime cost
as constobjects give runtime values + types- Enums emit JS objects
const enumissues with isolatedModules- Type stripping doesn't support enums
-
Most asked Which `tsconfig` strictness options matter and what do they catch?
Summary
strict: trueenablesstrictNullChecks,noImplicitAny,strictFunctionTypes,strictBindCallApply,strictPropertyInitialization,useUnknownInCatchVariables,alwaysStrictandnoImplicitThis. Extra recommended flags:noUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverrideandnoFallthroughCasesInSwitch.Longer Answer
strictNullChecksis the most valuable: it forces handlingnull/undefined.noUncheckedIndexedAccessmakesarr[i]andrecord[key]returnT | undefined, catching out-of-bounds bugs. When migrating a large JS codebase, enable strict flags incrementally.Key Points
strict: truebundlestrictNullChecksis the biggest winnoUncheckedIndexedAccessfor index safety- Adopt incrementally in migrations
-
Most asked How do function overloads work in TypeScript and when are they better than unions?
Summary
Overloads declare multiple call signatures for one implementation, letting the return type depend on the argument types. The implementation signature must be compatible with all overloads and isn't visible to callers.
Longer Answer
Use overloads when the output type depends on input in ways a union can't express (e.g.,
parse(x: string): Date; parse(x: number): Date[]). Often a generic or conditional return type is simpler. Overload order matters: TypeScript picks the first matching signature.Code Example
function createElement(tag: "a"): HTMLAnchorElement; function createElement(tag: "canvas"): HTMLCanvasElement; function createElement(tag: string): HTMLElement; function createElement(tag: string): HTMLElement { return document.createElement(tag); } const c = createElement("canvas"); // HTMLCanvasElementKey Points
- Multiple call signatures, one implementation
- Implementation signature hidden
- Order matters
- Consider generics/conditional types first
-
Most asked Why do you still need runtime validation in TypeScript, and how do libraries like Zod help?
Summary
TypeScript types vanish at runtime, so data from APIs, forms, env vars or
JSON.parseisn't actually checked. Schema libraries (Zod, Valibot, ArkType) validate data at runtime and infer static types from the same schema, giving one source of truth.Longer Answer
Validate at trust boundaries: incoming requests, external API responses, environment variables and local storage. Use
safeParseto handle errors without exceptions. Casting (as User) only silences the compiler; it doesn't make data correct.Code Example
import { z } from "zod"; const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), role: z.enum(["admin", "user"]), }); type User = z.infer<typeof UserSchema>; const res = await fetch("/api/me"); const parsed = UserSchema.safeParse(await res.json()); if (!parsed.success) throw new Error("Bad payload"); const user: User = parsed.data;Key Points
- Types are erased at runtime
- Validate at system boundaries
z.infer<typeof schema>for typesascasts don't validate
-
Most asked Explain `keyof`, `typeof` (in type position) and indexed access types.
Summary
keyof Tproduces a union ofT's property keys;typeof value(in a type position) gets the type of a runtime value; indexed accessT[K]looks up the type of a property (orT[number]for array elements).Longer Answer
Together they derive types from values:
type Config = typeof config; type Key = keyof Config; type Value = Config[Key]. This keeps types in sync with runtime objects.Key Points
keyof T→ union of keystypeof x→ type of a valueT[K]andT[number]lookups- Derive types from values
-
Most asked What are type assertions (`as`) and the non-null assertion (`!`), and why should you use them sparingly?
Summary
value as Ttells the compiler to treat a value asT, andvalue!asserts it isn'tnull/undefined. Neither does anything at runtime; if you're wrong, you get runtime errors the compiler could have caught.Longer Answer
Prefer narrowing, type guards or validation. Assertions are acceptable when you genuinely know more than the compiler (e.g., DOM elements guaranteed by the template), but
as unknown as Tdouble-casts are a code smell. ESLint rules can restrict them.Key Points
- Compile-time only, no runtime effect
- Prefer narrowing/validation
!hides possible undefined- Avoid
as unknown as T
-
Most asked What are intersection types and how do they differ from interface extension?
Summary
Intersection types (
A & B) combine types so the value must satisfy all of them. Interfaceextendsalso combines but reports conflicts eagerly, whereas conflicting intersection members can silently becomenever.Longer Answer
Intersections are useful for mixins, adding props (
T & { id: string }) and branding. Interfaces withextendsusually give better error messages and compiler performance for large object hierarchies.Key Points
A & Bmust satisfy both- Conflicts may produce
neversilently extendserrors on conflicts- Interfaces often faster to check
-
Most asked What are union types and how do you work with them safely?
Summary
A union type (
A | B) describes a value that can be one of several types. You can only use members common to all variants until you narrow the value with checks.Longer Answer
Unions model real-world variability:
string | numberIDs,'asc' | 'desc'options, or API results. Narrow withtypeof, discriminants or guards before using variant-specific members.Key Points
A | B= one of several types- Only shared members usable before narrowing
- Literal unions replace magic strings
- How do you type React component props, children and events in TypeScript?
- What are template literal types?
- What are covariance and contravariance in TypeScript function types?
- What are declaration files (`.d.ts`) and how do you type a JavaScript library without types?
- How do you type hooks like `useState`, `useReducer`, `useRef` and context?
- How do you write a generic React component (e.g., a typed List or Select)?
- How do module systems (ESM vs CommonJS) interact with TypeScript settings?
- What are `import type`, `isolatedModules` and `verbatimModuleSyntax`?
- How do `tsc`, esbuild, SWC and Babel differ for TypeScript builds?
- How would you migrate a large JavaScript codebase to TypeScript?
- How does type inference work in TypeScript, and when should you annotate explicitly?
- What's the difference between an optional property (`x?: T`) and `x: T | undefined`?
- How do optional chaining (`?.`) and nullish coalescing (`??`) work with types?
- Explain `void` as a return type and why a callback typed `() => void` can return a value.
- How do you model immutability in TypeScript?
- How do TypeScript access modifiers compare with JavaScript private fields?
- When would you use an abstract class versus an interface in TypeScript?
- What are index signatures and how do they relate to `Record`?
- How are tuples typed in TypeScript, including labeled and variadic tuples?
- Can Node.js run TypeScript directly?
- How do path aliases (`@/components`) work, and what's the common pitfall?
- What are project references and when do you use them?
- What are excess property checks and when do they apply?
- Why does `Object.keys(obj)` return `string[]` instead of `(keyof T)[]`?
- What's the difference between `// @ts-ignore` and `// @ts-expect-error`?
- What changed with decorators in TypeScript 5.0?
- What are `using` declarations (TypeScript 5.2)?
- What are `const` type parameters (TS 5.0) and `NoInfer` (TS 5.4)?
- How do you type JSON values or recursive structures?
- How should you handle errors in `catch` blocks in TypeScript?
- Why does `Omit` behave unexpectedly on union types, and how do you fix it?
- How do you test types themselves?
- How do you type an Express/Node API in TypeScript?
- How can you share types between frontend and backend?
- How does TypeScript type `this`?
- How do you keep TypeScript type-checking fast in large projects?
- What's the difference between `object`, `Object` and `{}` types?
- Why should you avoid the `Function` type?
- What is `strictPropertyInitialization` and the definite assignment assertion (`!:`)?
- What do `Parameters`, `ConstructorParameters`, `InstanceType` and `ReturnType` give you?
- Explain `Exclude`, `Extract` and `NonNullable`.
- How would you implement a `DeepPartial<T>` type?
- How do you type async functions and promises?
- How are generators and iterators typed?
- Should you use namespaces in modern TypeScript?
- How do you type environment variables safely?
- How do you publish a TypeScript library with correct types for ESM and CommonJS consumers?
- Can you get TypeScript checking in plain JavaScript files?
- What does typescript-eslint add beyond the compiler?
- Why are classes with private members compared nominally?
- Can you pass a `readonly string[]` to a function expecting `string[]`?
- How would you type an event emitter so event names and payloads are checked?
- How do you write a type-safe fetch wrapper?
- What is the Result pattern in TypeScript and when is it preferable to exceptions?
- How do you implement mixins in TypeScript?
- How can generics make a builder pattern type-safe (e.g., tracking which fields are set)?
- What is a `unique symbol` type?
- What do the `target` and `lib` compiler options control?
- What are generic defaults and when are they useful?
- What are phantom types and how do they relate to branding?
- How can `Record<Union, T>` catch bugs when a union grows?
- How are getters, setters and the `accessor` keyword typed?
- How do you add types to globals like `window`?
- What is contextual typing?
- What is literal type widening?
- What's the difference between `const`, `readonly` and `as const`?
- How do you type Maps, Sets and WeakMaps?
- Why combine `as const` with `satisfies`?
- How does the `in` operator narrow union types?
- How would you type a generic repository class?
- How do you safely use data from `JSON.parse` without a schema library?
- What's the difference between `string[]`, `Array<string>` and `[string]`?
- When should a function accept a union parameter versus using overloads?
- What is the native TypeScript compiler port (TypeScript 7 / tsgo)?
- Why can a function with fewer parameters be assigned to a function type with more parameters?
- How do you make a class implement an interface, and what does `implements` actually check?
- What do you do when TypeScript infers a generic type parameter too broadly or as `unknown`?
- How do you safely work with DOM elements in TypeScript?
- What should teams expect from TypeScript 6.0?
- What TypeScript practices do you look for in code reviews?