Free Handbook · Runs in your browser

Interview Questions

Sixty TypeScript interview questions in three tiers — junior, mid-level and senior — each with a model answer and what the question is actually testing.

0 / 105 lessons🔥 0 day streak
ShareXLinkedIn

Module 15 · what you'll be able to do

  • Answer the 25 questions every junior TypeScript screen draws from
  • Handle the 25 mid-level questions on discriminated unions, generics, utility types and the module system
  • Reason through the 10 senior questions on variance, type design and judgement
  • Recognise what an interviewer is actually testing behind each question, not just the syntax it asks about
01

Junior — 25 questions

These are asked in nearly every first-round TypeScript screen. Answer each out loud before opening it; an answer only recognised when read is not one that survives being asked under pressure.

JuniorWhat is the difference between interface and type?

Both describe object shapes and mostly overlap. Interfaces can be reopened later through declaration merging and read naturally for public object or class contracts; type can alias anything — unions, tuples, primitives, mapped types — not only objects. A common rule of thumb: default to interface for object shapes that might be extended, type for everything else, unions especially.

What they are really testing: Whether they know both exist for real reasons rather than picking one out of habit.

JuniorWhat does the strict compiler flag do?

It turns on a family of stricter checks together — noImplicitAny, strictNullChecks, strictFunctionTypes, strictPropertyInitialization and more. Without it, an untyped value silently becomes any and null/undefined are assignable to almost everything, which removes most of the value of using TypeScript at all.

What they are really testing: Whether they know strict is a bundle of flags, not one switch, and that turning it off is a trap rather than a shortcut.

JuniorWhat is type erasure?

TypeScript types exist only while tsc is checking the code; the compiled JavaScript carries no trace of them — no runtime type information, nothing like typeof MyInterface. This is why a type or interface can never be used to distinguish objects at runtime; that needs a real value, such as a string tag or an instanceof check.

What they are really testing: Whether they understand TypeScript adds zero runtime cost, and the direct consequence that a type can never gate runtime logic.

JuniorWhat is the difference between any and unknown?

any disables type checking entirely — methods can be called on it, it can be assigned anywhere, and the compiler trusts every use. unknown is type-safe: a value of any shape can be held in it, but nothing can be done with it until its type is narrowed first, with typeof, instanceof, or a type guard.

What they are really testing: The single most common line between a junior and a mid-level answer — do they reach for unknown at API boundaries instead of any.

JuniorWhat is the never type used for?

It represents a value that can never occur — a function that always throws, or one that never returns because it loops forever, or a variable narrowed until no possibilities remain. It shows up most often as the result of exhaustive narrowing, and is used deliberately in a switch’s default case to make the compiler flag any union member left unhandled.

What they are really testing: Whether it has been seen used for exhaustiveness checking, not only described as an abstract curiosity.

JuniorWhat is a generic, in one sentence?

A placeholder type parameter that lets a function, class or type work with any type while keeping the relationship between its inputs and outputs — function first<T>(items: T[]): T returns whatever type was given it, so calling it with string[] gives back a string, not any.

What they are really testing: Whether they connect generics to preserving type information end to end, rather than calling them a template with no clear purpose.

JuniorWhat is a union type?

A type that can be one of several types, written A | Bstring | number means either one. A method that is not common to every member cannot be called until the union is narrowed to which one is actually present.

What they are really testing: Basic syntax fluency.

JuniorWhat is an intersection type?

A type that combines several types into one, written A & B — the result must satisfy every member at once. It is used to compose smaller interfaces into a larger required shape, for example type Admin = User & { permissions: string[] }.

What they are really testing: Whether union and intersection can be told apart by what a value must satisfy — one of, versus all of.

JuniorWhat does a ? after a property name mean?

The property is optional — it may be omitted entirely, and its type is really T | undefined. Reading it without a guard needs a null check or optional chaining, since strictNullChecks flags using it as if it were always present.

What they are really testing: Fluency with syntax that appears in nearly every interface.

JuniorWhat does readonly do on a property?

It prevents reassignment after the object is created — obj.field = x is a compile error outside the constructor. It is a compile-time guarantee only; nothing stops the property being mutated through a cast or from code outside TypeScript's view, so it documents intent more than it enforces safety.

What they are really testing: Whether they know it is compile-time only, not a runtime freeze — often confused with Object.freeze.

JuniorWhat does a TypeScript enum compile to?

A regular numeric or string enum compiles to a real JavaScript object, with both forward and reverse mappings for numeric enums (Colors.Red === 0 and Colors[0] === "Red"), so it has an actual runtime footprint, unlike almost everything else in TypeScript. const enum is inlined at compile time and leaves no object behind, but is incompatible with some build tools that use isolated-file transpilation.

What they are really testing: Whether they know enums are one of the few TypeScript features that survives compilation as real code.

JuniorWhat is a literal type?

A type that is one specific value rather than a category — type Direction = "up" | "down" restricts a variable to exactly those two strings, not any string. Combined into unions, literal types are how TypeScript models enum-like values without a runtime enum.

What they are really testing: Connects directly to discriminated unions, covered at mid level.

JuniorWhat is the difference between a type assertion (as) and an actual type conversion?

as tells the compiler "trust me, treat this value as this type" — it performs no runtime check or conversion, it only silences the checker. Number(x) or String(x) actually convert the value. Asserting the wrong type compiles cleanly and fails at runtime exactly where a real bug would.

What they are really testing: Whether they know as is a promise made to the compiler, not a safety net.

JuniorWhat is a tuple type?

A fixed-length array where each position has its own type — [string, number] is exactly a string followed by a number, not an array of either. Useful for things like a [value, setValue] pair, where position itself carries meaning.

What they are really testing: Whether they have used one in practice, typically via a library, rather than only recognising the syntax.

JuniorWhat is the difference between a function returning void and one returning undefined?

void means "the return value should be ignored" — callback-shaped code such as a forEach handler or an event listener uses it, and a function can even return something and still be assignable to a void-typed slot. undefined is a real, checkable type; declaring it as the return type means callers are expected to look at the always-undefined result.

What they are really testing: A subtle distinction most juniors have not needed until typing a callback parameter themselves.

JuniorCan two functions with the same name but different parameter types coexist in TypeScript?

Yes, through function overload signatures — several signature declarations followed by one implementation whose parameters are typed loosely enough to satisfy all of them. Callers only ever see the specific overloads, not the implementation signature, so autocomplete matches whichever one they are calling.

What they are really testing: Recognition of the feature, even without having designed one — rarely needed at junior level.

JuniorWhat is a type guard?

A runtime check that narrows a variable's type within a block — after if (typeof x === "string"), TypeScript treats x as string for the rest of that branch. It is how a union type is narrowed back down to something specific enough to use.

What they are really testing: Whether narrowing is understood as something TypeScript infers from ordinary JavaScript checks, not special syntax.

JuniorHow does typeof narrow a type?

Inside if (typeof value === "number"), TypeScript narrows value to number for that branch — it only works for the primitive categories typeof can distinguish (string, number, boolean, undefined, function, object, symbol, bigint), not for telling two object shapes apart.

What they are really testing: Whether the limits of typeof narrowing are known — where it stops working is the natural follow-up.

JuniorHow does instanceof narrow a type?

if (error instanceof RangeError) narrows error to RangeError inside that block. It works because instanceof checks the prototype chain at runtime, something TypeScript can trust since there is a real class behind it, unlike a purely structural type.

What they are really testing: Whether class-based narrowing is connected to typeof's primitive-only narrowing as a distinct mechanism.

JuniorWhat does ! after an expression do — the non-null assertion operator?

It tells the compiler "this is not null or undefined, stop warning me" — document.getElementById("x")!.focus(). It performs no runtime check; being wrong throws exactly where a real null bug would, just later and with less context than a real guard would have given.

What they are really testing: Whether it is understood as a compiler-only suppression, and when it is a code smell versus genuinely necessary — DOM APIs are the usual legitimate case.

JuniorWhat is structural typing, and how does it differ from nominal typing?

TypeScript compares types by shape — two objects with the same properties are compatible regardless of what either is named or whether one was ever declared to implement the other. Nominal typing, as in Java or C#, instead requires an explicit declaration to be considered compatible. This is why an object literal with the right fields satisfies an interface without ever mentioning it.

What they are really testing: Whether they understand why TypeScript accepts values that were never declared against the interface — the most surprising thing to anyone coming from a nominally-typed language.

JuniorWhat is the difference between number[] and Array<number>?

None — they are two syntaxes for the exact same type. T[] is the shorthand generally preferred for simple element types; Array<T> is sometimes clearer for more complex element types, such as a union or a function type.

What they are really testing: Confirms they know it is purely stylistic, not two different types.

JuniorHow do you type a function parameter that has a default value?

TypeScript infers the type from the default itself — function greet(name = "friend") infers name: string — so an explicit annotation is often unnecessary. One is only needed when the inferred type is narrower than how the parameter is actually meant to be used.

What they are really testing: Whether they over-annotate out of habit, the same instinct the "let it infer" rule from Module 00 already warns against.

JuniorWhat does noImplicitAny do, specifically?

It errors on any parameter, variable or return value whose type TypeScript cannot infer and that has no annotation, instead of silently treating it as any. It is one of the flags bundled into strict, and is usually the very first error a real, previously untyped JS file produces once converted to TS.

What they are really testing: The exact flag behind TS7006, since it is the single most common first error in any migration.

JuniorWhat is the difference between const x = 5 and const x = 5 as const?

For a plain primitive like 5, const alone already narrows it to the literal type 5 rather than widening to number. The real difference shows on objects and arrays: const p = { x: 1 } infers { x: number }, while const p = { x: 1 } as const infers { readonly x: 1 } — every property becomes readonly and keeps its specific literal type instead of widening.

What they are really testing: Whether as const has been used for literal-type inference on objects, arrays or tuples — a very common modern pattern for config objects and action types.

02

Mid-level — 25 questions

For roles with two to five years of experience. The interviewer is checking whether the type system is understood as a design tool — discriminated unions, generic constraints, the utility types — not only used to silence red squiggles.

Mid-levelExplain discriminated unions, and why switching on the tag field can be checked for exhaustiveness.

A discriminated union is a union of object types sharing one literal-typed field — the "tag" or "discriminant", such as kind: "circle" | "square". Switching on that field lets TypeScript narrow which variant is present inside each case, and assigning the switch’s fallthrough to a variable typed never in the default case makes the compiler error if a new variant is ever added and left unhandled — exhaustiveness checking for free.

What they are really testing: Whether the never-in-default idiom has actually been used, the line between people who have read about discriminated unions and people who ship them.

Mid-levelWhat is a generic constraint, and why use extends in <T extends { id: number }>?

It restricts what T is allowed to be — without a constraint, T could be anything, and the function body could only use operations valid on every possible type, which is essentially none. <T extends { id: number }> says T can be any type as long as it has an id: number, letting the function safely read .id while still accepting whatever object shape qualifies, and keeping the specific input type on the way out.

What they are really testing: Whether constraints are understood as narrowing what is allowed in, not narrowing what comes out — a common confusion.

Mid-levelName the utility types Partial, Pick and Omit, and what each does.

Partial<T> makes every property of T optional, useful for a patch or update payload. Pick<T, K> builds a new type with only the listed keys K from T. Omit<T, K> is the inverse — every key of T except the listed ones. All three are built from TypeScript’s mapped types underneath.

What they are really testing: The three most-used utility types in real codebases — near-certain to come up when discussing an update endpoint’s type.

Mid-levelWhen would a function boundary use unknown instead of a specific type, and what does that force the caller to do?

At a boundary where the value genuinely is not known yet — parsing JSON, a catch block’s error, a third-party webhook payload. Unlike a specific type, which lets the caller act immediately whether or not that is correct, or any, which lets them act immediately and unsafely, unknown forces a runtime check, a validation library, or a type guard before the value can be used at all.

What they are really testing: Whether unknown is understood as a forcing function for validation, not simply "any but safer".

Mid-levelWhat is an excess property check, and when does it not apply?

Assigning an object literal directly to a typed variable triggers a check that flags any property not present on the target type — const u: User = { name: "Ada", extra: 1 } errors even though structurally a User with an extra field would normally be assignable. It does not apply once the value passes through a variable first: const obj = { name: "Ada", extra: 1 }; const u: User = obj compiles, because structural typing allows extra properties on an already-typed value.

What they are really testing: One of the most-asked gotcha questions — whether they know structural typing is looser than the literal-assignment check makes it appear.

Mid-levelWhat does keyof do?

It produces a union of an object type’s property-name literal types — keyof User for { name: string; age: number } is "name" | "age". It is the basis for type-safe property accessors, such as a generic get<T, K extends keyof T>(obj: T, key: K): T[K].

What they are really testing: Whether keyof is connected to a concrete, type-safe use case rather than recited as a definition.

Mid-levelWhat does typeof mean when used in a type position, as in type Config = typeof defaultConfig?

It is a different operator from the runtime typeof — used where a type is expected, it takes the compile-time type of a value or variable. It is how a type is derived from an existing object without duplicating the shape by hand, common for config objects and constants.

What they are really testing: Whether they know TypeScript overloads the keyword typeof for two purposes depending on position — usually a real realisation moment for anyone still thinking purely in JavaScript.

Mid-levelWhat is a mapped type? Give an example beyond Partial.

A type built by iterating over the keys of another type and transforming each property — { [K in keyof T]: T[K] } is the identity mapped type; adding modifiers builds real ones, for example type Nullable<T> = { [K in keyof T]: T[K] | null } makes every property nullable. Partial, Required and Readonly are all mapped types defined exactly this way in the standard library.

What they are really testing: Whether a small custom one can be written, not only the built-ins named.

Mid-levelWhat is a conditional type? Write the shape of one.

A type-level if/else: T extends U ? X : Y — if T is assignable to U, the type is X, otherwise Y. type IsString<T> = T extends string ? true : false is the minimal example; a distributive conditional type applies the check to each member of a union T separately, which is how Exclude and Extract are implemented.

What they are really testing: Basic syntax plus the distributive behaviour over unions, the part most people miss.

Mid-levelWhat does the infer keyword do inside a conditional type?

It introduces a new type variable to capture part of the type being matched — type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never pulls out a function’s return type. It is how TypeScript’s own ReturnType and Parameters utility types are implemented.

What they are really testing: Whether one has actually been written or at least read — a real dividing line between using TypeScript and writing its type system.

Mid-levelHow does TypeScript pick which overload signature applies to a call?

It tries each declared overload signature in order, top to bottom, and uses the first one the call’s arguments are assignable to — not the best match by any other measure. This is why overload order matters: put the more specific signatures first, or a looser earlier one shadows them.

What they are really testing: Whether overload resolution is known to be order-dependent rather than best-fit — a real source of confusing bugs when overloads are added carelessly.

Mid-levelHow does the in operator narrow a union of object types?

if ("swim" in animal) narrows animal to the union members whose type actually declares a swim property, useful when the union is not already discriminated by a clean tag field. It is the fallback narrowing technique for shapes not designed with a discriminant in mind.

What they are really testing: Whether an alternative exists to the tag-based discriminated union pattern, for shapes not under one’s control.

Mid-levelWhat is an index signature, and what is the risk of using one?

{ [key: string]: number } says an object can have any string key, all mapped to a number — used for dictionaries whose keys are not known ahead of time. The risk: TypeScript lets obj[anyRandomKey] be read as a number even for a key that was never set, returning undefined at runtime with no type-level warning unless noUncheckedIndexedAccess is enabled.

What they are really testing: Whether noUncheckedIndexedAccess is known — one of the strict-adjacent flags most projects should turn on but rarely do.

Mid-levelWhat is the difference between Readonly<T> and ReadonlyArray<T> / readonly T[]?

Readonly<T> is a mapped type making every property of an object type T readonly. ReadonlyArray<T> (or its shorthand readonly T[]) is the array-specific version, and it removes access to mutating methods like push and splice from the type entirely, not just individual elements. Both are compile-time only; neither freezes anything at runtime.

What they are really testing: Whether readonly arrays are known to block mutator methods at the type level — a stronger guarantee than making each element readonly.

Mid-levelWhat is a .d.ts file, and why does it exist?

A declaration file holds only type information, no implementation, describing the shape of JavaScript TypeScript cannot see into on its own — most often a library’s public API. Publishing a TypeScript package emits a .d.ts alongside the .js so consumers get types without the source; consuming a plain-JS library, its .d.ts (often from @types/) is what gives autocomplete and checking against it.

What they are really testing: Foundational for the "publish a typed npm package" portfolio project in Module 16.

Mid-levelName two utility types other than Partial, Pick and Omit, and what each does.

Record<K, V> builds an object type with keys K all mapped to value type V — a typed dictionary. Required<T> makes every optional property mandatory, the inverse of Partial. Also worth knowing: Exclude/Extract to filter a union, ReturnType for a function’s return type, Parameters for its parameter tuple.

What they are really testing: Breadth beyond the three most common ones — whether the standard-library toolbox is genuinely familiar.

Mid-levelHow do you give a generic type parameter a default, and when is that useful?

interface Box<T = string> — if the caller does not specify T, it defaults to string. Useful for a generic that is usually used one way, such as a form field or an API response wrapper, but should stay generic for the less common cases, without forcing every caller to specify the type parameter explicitly.

What they are really testing: A smaller, easily-missed feature — whether it is reached for instead of duplicating a generic and a non-generic version of the same type.

Mid-levelWhat does the satisfies operator do, and how does it differ from a type annotation?

const config = { mode: "dark" } satisfies Config checks the value against Config without widening or replacing its inferred type — unlike const config: Config = {...}, which forces every property to exactly the type Config declares. With satisfies, config.mode keeps the literal type "dark" instead of widening to string, so both the validation and the precise inferred type are kept.

What they are really testing: A relatively recent feature (TypeScript 4.9+) — whether they have kept up with the language and understand the widening problem it solves.

Mid-levelWhen would you use an abstract class instead of an interface?

An interface is purely a type with zero runtime code, and a class can implement several. An abstract class can hold shared implementation — fields, concrete methods — alongside abstract members that subclasses must fill in, but a class can only extend one. Reach for abstract classes when subclasses genuinely share behaviour, not only a shape; reach for interfaces for the shape alone, or when unrelated classes need to satisfy the same contract.

What they are really testing: Whether abstract classes are known to carry real runtime code and single inheritance, unlike interfaces.

Mid-levelWhat is the difference between TypeScript's private/protected and JavaScript's #private fields?

private/protected are compile-time only — the emitted JavaScript has a completely ordinary property, reachable at runtime through bracket notation or a type assertion; they exist purely to catch mistakes during development. #field is a real ECMAScript private field enforced by the JavaScript engine itself, genuinely inaccessible from outside the class at runtime, in any language.

What they are really testing: Whether TypeScript's access modifiers are known to offer zero runtime protection — relevant if a candidate has ever assumed otherwise.

Mid-levelWhat is a template literal type? Give an example.

A type built like a template string but at the type level, combining literal types — type Event = "click" | "hover"; type Handler = `on${Capitalize<Event>}` produces the union "onClick" | "onHover". Used for things like CSS-in-JS property names or event handler prop names derived from a smaller set of event names.

What they are really testing: A newer feature (TypeScript 4.1+) — depth of type-system fluency past the basics.

Mid-levelWhat is the difference between Exclude<T, U> and Extract<T, U>?

Both filter a union T against U. Exclude<T, U> keeps the union members NOT assignable to U — removal. Extract<T, U> keeps only the members that ARE assignable to U — selection. They are mirror images, both implemented as distributive conditional types.

What they are really testing: Whether the two are actually remembered correctly — commonly swapped even by people who use them regularly.

Mid-levelHow is never used for exhaustiveness checking in a switch statement?

In the default case of a switch over a union, assign the by-then-narrowed-to-nothing remaining value to a variable typed never: default: { const check: never = state; throw new Error("unhandled") }. If a new union member is added later and its case forgotten, that member is no longer narrowed away by the time it reaches default, so it is no longer assignable to never, and the compiler errors right there instead of the gap reaching runtime.

What they are really testing: The concrete mechanism, not just "never means it cannot happen" — whether they would actually use it in a pull request.

Mid-levelWhat is a realistic use for Record<K, V> beyond a simple dictionary?

Typing an object that must have exactly one entry per member of a union or enum — Record<Status, string> for type Status = "idle" | "loading" | "error" forces a label to be provided for every status, and the compiler errors if one is missing or a mistyped key is added. It turns "did I handle every case" into a type error instead of a runtime gap.

What they are really testing: Whether Record is understood as a completeness tool, not only a loose dictionary type.

Mid-levelWhat does import type do, and why does it matter for build tooling?

import type { Config } from "./config" imports only the type, guaranteed to be erased completely at compile time — it can never accidentally pull in runtime code or create a circular runtime dependency. It matters for bundlers and transpilers that process files one at a time, since they cannot always tell from a plain import whether a name is a type or a value; a type-only import removes the ambiguity.

What they are really testing: Whether an isolatedModules error has actually been hit and fixed in a real build, versus never having used a bundler that needed it.

03

Senior — 10 questions

Senior questions have no single right answer. They test judgement: how a problem is diagnosed, what would be asked first, what would be refused, and how a design is explained to someone who has to maintain it. The answers here show the shape of a strong response, not a script.

SeniorExplain variance in TypeScript's function types — why is a function parameter type checked contravariantly?

For a function type to safely substitute where another is expected, its parameters must accept at least as much as required — contravariant, a wider or equal parameter type is safe — while its return type must provide at least as much as promised — covariant, a narrower or equal return type is safe. Concretely, (x: Animal) => void is assignable where (x: Dog) => void is expected, because a function that can handle any Animal can certainly handle a Dog; the reverse would let a Dog-only handler receive a Cat. TypeScript actually checks method parameters bivariantly for backward-compatibility reasons, a known unsoundness most teams accept.

What they are really testing: Genuine type-theory depth — most candidates have never had to name this, only intuit it.

SeniorDesign a DeepPartial<T> type that makes every property optional, recursively, including nested objects and arrays.

A mapped type combined with a conditional to recurse into object and array members: type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T. The array branch recurses into the element type so arrays themselves are not turned optional; the base case, a primitive, returns T unchanged so the recursion terminates.

What they are really testing: Whether a non-trivial recursive type can actually be constructed live, not only described.

SeniorWhat is declaration merging, and give a real example of when it is useful?

TypeScript merges multiple declarations of the same name in certain cases instead of erroring — two interface declarations with the same name combine their members into one; a namespace can merge with a class or function to attach static-like members. The most common real use: extending a third-party library's types by re-declaring one of its interfaces in a local .d.ts, such as adding a custom property to Express's Request without forking the library.

What they are really testing: Whether the Express Request extension has actually been done — a rite of passage for anyone who has added middleware-set data to a typed Express app.

SeniorWalk through extending Express's Request type with a user property via module augmentation.

In a .d.ts file included in the project: declare global { namespace Express { interface Request { user?: { id: string } } } }, plus an empty export {} to keep the file a module. Because Request is an interface, this declaration merges with Express's own, so every req: Request in the app now has a typed, optional user field without touching node_modules or Express's source.

What they are really testing: The exact mechanics — the empty export, the global augmentation, the interface-not-type requirement — versus a hand-wavy "you can extend it somehow".

SeniorA generic type is making the editor noticeably slow, and tsc's check time has ballooned. What causes that, and what would you do?

Usually a deeply recursive conditional or mapped type, especially one that fans out over a large union or recurses without a clear base case near the recursion limit — the checker has to enumerate combinations instead of doing simple structural comparison. Profiling with tsc --generateTrace finds the actual hot type; the fix is to simplify it — cap recursion depth explicitly, replace a huge union with a more general constraint, precompute an intermediate named type so the checker can cache it, or in the worst case fall back to a less precise type with a comment explaining why.

What they are really testing: Whether this has been hit in production, versus only heard to be theoretically possible — real answers name a specific diagnostic tool.

SeniorDesign the types for a small, type-safe REST API client. What would the generic signature of its get method look like?

Something like get<Path extends keyof Routes>(path: Path): Promise<Routes[Path]["response"]>, where a Routes interface maps each literal path string to its response (and request) shape, generated from the API's schema or hand-maintained alongside it. Calling client.get("/users/:id") then returns a correctly-typed response with no per-call casting, and adding a new endpoint to Routes makes every existing call to it type-check against the new shape immediately.

What they are really testing: Whether the design can be produced from scratch rather than only consuming a pre-built client — a strong signal for anyone who will own internal tooling.

SeniorTypeScript is structurally typed, so two differently-named types with the same shape are interchangeable. How would you simulate nominal typing for something like a UserId that should not be mixed up with a plain number?

The common trick is a branded type: intersect the real type with an unused, unique tag — type UserId = number & { readonly __brand: "UserId" }. A plain number is no longer assignable to UserId without an explicit cast, so a function expecting a UserId cannot accidentally receive an OrderId or a raw index, even though both are numbers underneath. A small helper function performs the cast in exactly one place.

What they are really testing: One of the more advanced, genuinely useful senior patterns — whether it has been needed for something like preventing id mix-ups in a large codebase.

SeniorWhen designing a public library's API, when would you choose to accept unknown instead of a generic type parameter?

When the caller's input truly cannot be validated by the type system alone — parsing untrusted JSON, a webhook body, plugin config loaded from a file — unknown forces every caller to run a runtime check or validator before the library trusts the shape, which is honest about the actual guarantee being made. A generic type parameter is the right call when the caller genuinely knows and controls the type and the library only needs to preserve it through, as a typed cache or collection does.

What they are really testing: Whether the choice is grounded in what can actually be verified at the boundary, not applied reflexively either way.

SeniorWhat do isolatedModules and project references solve, and when are they needed?

isolatedModules is required by any transpiler — esbuild, swc, Babel — that compiles one file at a time without full program knowledge; it forces patterns tsc alone would not need, like type-only exports being explicit and no const enums, because a single-file transpiler cannot resolve cross-file type information. Project references split a large codebase into separately-built sub-projects with cached, incremental builds, needed once a monorepo's full type check takes long enough to slow down CI or local iteration.

What they are really testing: Real build-tooling scars — whether either has actually been configured, not only the flag names recognised.

SeniorHow would you review a junior engineer's pull request that adds as any to silence a type error, and what would you ask for instead?

First, understand what the checker was actually complaining about — as any sometimes covers a real bug, a genuinely wrong shape, rather than a checker limitation. If the type is genuinely awkward to express, the narrowest fix that keeps the guarantee is worth asking for: a proper type guard, a small utility type, or, if truly unrepresentable, as unknown as T with a comment explaining why — which at least documents the loss of safety instead of hiding it behind a type that also disables every future check on that variable. any is contagious: once it enters an expression, it silently spreads to everything downstream that touches it, which is the concrete cost worth making visible.

What they are really testing: Judgement and mentoring, not only technical correctness — the same shape as a senior code-review question in any language.

Finish the TypeScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.