Skip to content
tezvyn:

Top 30 Advanced TypeScript & Web APIs Interview Questions and Answers

30 advanced multiple-choice TypeScript & Web APIs interview questions, the deep end: internals, failure modes, and the design calls a senior engineer is expected to defend. They come from 30 bites in the TypeScript & Web APIs library, the hardest slice of the 125 TypeScript & Web APIs interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.

TypeScript, browser APIs, WebAssembly, PWAs

30 questions. Pick an answer, or open “Show the answer” to read it.

Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.

  1. Question 1 of 30

    Which getProperty signature both rejects invalid keys at compile time and returns the exact type of the requested property?

    Show the answer

    Answer: c · Two generics T and K where K extends keyof T and the return type is T[K]

    Option C is correct because constraining K with extends keyof T limits keys to valid properties on T, while T[K] preserves the exact type of the accessed property. Option D is tempting because keyof T does restrict keys, but T[keyof T] produces a union of all property types rather than the specific one for the key passed.

    Read the full bite: Create a generic getProperty using generics and keyof

  2. Question 2 of 30

    What makes a user-defined type guard unsound when its predicate claims value is User but the body only verifies value is not null?

    Show the answer

    Answer: d · TypeScript narrows the type in conditional branches while the value may still lack required User properties at runtime

    A type predicate is purely a compile-time hint; if the runtime check is too permissive, TypeScript narrows the type based on a false promise, allowing unsafe property access. Distractor B is wrong because TypeScript never mutates or coerces runtime values to satisfy a type predicate.

    Read the full bite: What is a type predicate? Write a custom type guard for User.

  3. Question 3 of 30

    When passing a large ArrayBuffer from the main thread to a dedicated Web Worker for image processing, which technique avoids duplicating that memory in the browser?

    Show the answer

    Answer: c · Include the ArrayBuffer in the postMessage transfer list so ownership moves to the worker

    Listing the ArrayBuffer in the transfer list moves ownership to the worker without copying, preventing memory duplication. The most tempting distractor is wrong because structured clone always copies data; postMessage never shares memory by reference between threads.

    Read the full bite: Offload CPU-intensive work to a Web Worker and explain communication.

  4. Question 4 of 30

    Which statement accurately compares open and closed shadow modes regarding encapsulation and event propagation?

    Show the answer

    Answer: b · Closed mode returns null for external shadowRoot access but does not prevent events from bubbling out retargeted to the host

    Closed mode returns null for element.shadowRoot to prevent accidental access, yet bubbling events still cross the boundary with their target retargeted to the host. Distractor D is tempting because closed sounds like a security boundary, but the platform explicitly does not protect against malicious inspection.

    Read the full bite: Explain Shadow DOM, encapsulation, and event propagation

  5. Question 5 of 30

    Which approach is the idiomatic TypeScript solution for constructing a typesafe ApiRoute type that includes both /api/v1/<resource> collection paths and /api/v1/<resource>/{id} item paths from a finite Resource union?

    Show the answer

    Answer: b · A base route template interpolating Resource into /api/v1/, unioned with the same base route suffixed by /{id}

    Template literal types automatically distribute a union through an interpolated position into every concrete string permutation, so unioning BaseRoute with BaseRoute/{id} is the idiomatic, machinery-free solution. Option A is tempting because it yields the same members, but it unnecessarily uses a mapped type when direct interpolation already expands the union, and C sacrifices compile-time exhaustiveness for false flexibility.

    Read the full bite: Build a typesafe ApiRoute type using template literal types

  6. Question 6 of 30

    What return type does fetchItem<T extends boolean>(..., includeHistory: T): T extends true ? ExtendedItem : BaseItem yield when includeHistory is a plain boolean variable?

    Show the answer

    Answer: c · The type resolves to ExtendedItem | BaseItem because boolean encompasses both true and false

    Because boolean is equivalent to true | false, the distributive conditional type evaluates both branches and produces a union. Option B is tempting but wrong because TypeScript does not narrow a plain boolean variable to a literal across a function boundary.

    Read the full bite: How would you model fetchItem's return type with generics and conditional types?

  7. Question 7 of 30

    In TypeScript, which scenario best justifies using an abstract class rather than an interface?

    Show the answer

    Answer: b · Subclasses must share stateful logic and a constructor that enforces initialization order at runtime.

    An abstract class is warranted when subclasses need shared runtime state or guaranteed initialization, such as an internal cache, because interfaces are erased after compilation and cannot carry fields or constructor logic. Option D is tempting because abstract classes can contain implemented methods, but sharing a stateless helper method consumes the single extends slot unnecessarily when composition or a utility function could achieve the same result.

    Read the full bite: Difference between abstract class and interface, with scenario

  8. Question 8 of 30

    In the ResponsiveCSSBuilder example, what happens when setColor returns CSSBuilder instead of this during method chaining?

    Show the answer

    Answer: a · The chained type collapses to CSSBuilder, making subclass methods like setBreakpoint inaccessible.

    Returning CSSBuilder explicitly collapses the type to the base class, so subsequent subclass-specific methods become inaccessible in the chain, whereas this preserves the derived type. Distractor A confuses static type narrowing with runtime prototype behavior, which is a common misconception the card explicitly warns against.

    Read the full bite: What is the polymorphic this type in TypeScript?

  9. Question 9 of 30

    What role does the private constructor play in a TypeScript Singleton implementation?

    Show the answer

    Answer: c · It prevents external code from using new, forcing access through getInstance

    A private constructor blocks new MyClass at compile time, so callers must use the controlled getInstance accessor. It does not encrypt fields, provide thread safety, or by itself make the field lazy.

    Read the full bite: Implementing the Singleton pattern in TypeScript

  10. Question 10 of 30

    When appending 1,000 newly created elements in a loop, why does using a DocumentFragment improve performance over direct parent append?

    Show the answer

    Answer: c · It exists outside the active document tree, so appends do not trigger reflows or repaints

    C is correct because a DocumentFragment lives outside the active document tree, so batch appends inside a loop cause zero reflows until the single final insertion. A is a tempting distractor because beginners often wrongly assume fragments reduce memory usage, but they actually minimize layout calculations.

    Read the full bite: How would you use DocumentFragment to optimize adding 1,000 list items?

  11. Question 11 of 30

    Which pattern correctly implements an IntersectionObserver callback in TypeScript for lazy-loading images while preserving type safety and memory efficiency?

    Show the answer

    Answer: a · Type entries as IntersectionObserverEntry[], narrow entry.target with instanceof HTMLImageElement, and call observer.unobserve(entry.target) after setting src

    The callback receives IntersectionObserverEntry[], and because entry.target is typed as Element, you must narrow it to HTMLImageElement before setting src and then call observer.unobserve(entry.target) to release that node. Option C is tempting because the array type is correct, but HTMLElement is still too broad for src and disconnect stops all observations instead of just the loaded image.

    Read the full bite: Implement IntersectionObserver in TypeScript for lazy-loading images

  12. Question 12 of 30

    When building a single TypeScript handler for mixed form inputs, which strategy correctly preserves type safety when extracting values from checkboxes, text inputs, and selects?

    Show the answer

    Answer: b · Narrow event.target with instanceof checks and branch on element.type to read .checked or .value.

    Narrowing with instanceof and branching on element.type lets the compiler verify you read .checked for checkboxes and .value for other elements. Option D is tempting because it uses a union type, but reading .value from a checkbox gives the string on rather than the boolean state, breaking both type safety and runtime logic.

    Read the full bite: Write a generic TypeScript handler for mixed form inputs?

  13. Question 13 of 30

    In a search-as-you-type component using AbortController, what must occur before initiating a new fetch on each keystroke?

    Show the answer

    Answer: c · Abort the previous controller, instantiate a new one, and pass its fresh signal

    You must abort the previous in-flight request and create a fresh AbortController per keystroke because a signal can only be aborted once. Reusing the same AbortSignal is a tempting mistake because once aborted it would immediately reject all subsequent fetches.

    Read the full bite: How would you prevent search-as-you-type race conditions with AbortController?

  14. Question 14 of 30

    Which pattern correctly implements inSequence to execute promise-returning tasks sequentially and return ordered results?

    Show the answer

    Answer: d · Declare an async function that iterates tasks with a for...of loop, awaits each task() call, and accumulates results

    A for...of loop inside an async function blocks on each awaited task call, guaranteeing sequential execution and ordered results. Using forEach with an async callback fires every task immediately because forEach does not await its callback, so tasks run concurrently and the function returns before they finish.

    Read the full bite: Implement inSequence(tasks) to execute promise-returning functions sequentially

  15. Question 15 of 30

    When wiring AbortController to a rapid-type search input, which pattern prevents stale UI results without producing unhandled promise rejections?

    Show the answer

    Answer: d · Call abort() on the prior controller if it exists, then instantiate a fresh one and pass its signal to fetch, catching only errors named AbortError

    You must abort the prior controller before creating a fresh one, pass its signal, and suppress only AbortError to avoid unhandled rejections. Reusing the same controller is tempting but wrong because an aborted signal never resets, so subsequent fetches would abort immediately.

    Read the full bite: How do you cancel pending fetches using AbortController?

  16. Question 16 of 30

    A React app on https://app.example.com sends a cross-origin POST to https://api.other.com/events with Content-Type application/json and an Authorization header. Why does the browser first send an OPTIONS request?

    Show the answer

    Answer: b · The combination of application/json Content-Type and the Authorization header makes it a complex cross-origin request.

    The correct answer reflects that application/json is not a CORS-safelisted Content-Type and Authorization is a custom header, so the browser must preflight the complex cross-origin request. The most tempting distractor incorrectly suggests the server initiates the OPTIONS call to verify auth, whereas the card states the browser, not the server, enforces CORS by sending the preflight.

    Read the full bite: When does fetch trigger a CORS preflight, and what POST is complex?

  17. Question 17 of 30

    What is the immediate consequence of calling response.body.pipeTo() after already obtaining a reader from that same body?

    Show the answer

    Answer: a · A TypeError is thrown because the stream is locked to the reader

    Calling getReader() locks the ReadableStream, and the card explicitly states that any subsequent attempt to pipe or tee it will throw a TypeError. Option C is a tempting distractor because queuing feels intuitive, but locked streams fail immediately rather than deferring the pipe operation.

    Read the full bite: Download a large file via fetch and track ReadableStream progress

  18. Question 18 of 30

    What is the primary type-system purpose of resolving to never in the false branch of MyReturnType<T>?

    Show the answer

    Answer: d · It prevents non-function types from being silently assignable by making invalid usages immediately unassignable

    The never branch preserves type safety by ensuring non-function types produce an unassignable type, which stops invalid usage at compile time. Leaving the false branch as T or unknown would silently allow non-functions to pass through, a common mistake.

    Read the full bite: Implement MyReturnType<T> using conditional types and infer

  19. Question 19 of 30

    How does the as key-remapping syntax in PickByType differ from mapping all keys and setting non-matching values to never?

    Show the answer

    Answer: d · The as clause removes non-matching keys entirely, while the never-value approach preserves them with never types.

    The card notes that setting non-matching values to never preserves keys instead of removing them, whereas the as clause filters keys before mapping. Distractor D is tempting because developers often assume never properties disappear, but TypeScript retains the key with a never value.

    Read the full bite: Implement a generic PickByType<T, U> utility type

  20. Question 20 of 30

    Which mapped type correctly implements PickByValue<T, V>, keeping only keys whose values are assignable to V?

    Show the answer

    Answer: b · { [K in keyof T as T[K] extends V ? K : never]: T[K] }

    The correct answer uses the as clause to drop keys by mapping them to never while preserving original values as T[K]; option D reverses the assignability check so it keeps properties whose types are supertypes of V rather than subtypes.

    Read the full bite: Write a generic PickByValue<T, V> type

  21. Question 21 of 30

    When implementing DeepReadonly<T>, what is the main drawback of letting array types fall into the object mapped-type branch instead of handling them separately with ReadonlyArray?

    Show the answer

    Answer: a · It produces a plain object type with numeric keys and strips array methods like push and pop.

    Mapping over keyof an array yields a plain object type with numeric keys, which strips away array methods like push and pop and destroys array semantics. Option C is a common misconception because the object branch would still recursively wrap values; the critical loss is array type identity and methods, not element mutability.

    Read the full bite: Implement recursive DeepReadonly<T> for nested objects and arrays

  22. Question 22 of 30

    When defining CreateSetters<T>, why is the string & K intersection required inside Capitalize?

    Show the answer

    Answer: d · Because keyof T may yield symbol keys, and Capitalize only accepts string subtypes

    keyof T can include symbol keys, yet Capitalize only accepts string subtypes, so string & K is required to satisfy that constraint. Option B is tempting but wrong because Capitalize can work with generic string parameters; the intersection guards against non-string keys rather than being a generic requirement.

    Read the full bite: Write a generic CreateSetters<T> that maps properties to setter methods

  23. Question 23 of 30

    Which approach correctly adds an index to an existing IndexedDB store while preserving data and handling production constraints?

    Show the answer

    Answer: c · Increment the version in indexedDB.open, conditionally create the index inside onupgradeneeded based on event.oldVersion, and attach an onblocked handler for other tabs.

    IndexedDB schema mutations require a version bump and must run inside onupgradeneeded, where event.oldVersion enables incremental idempotent migrations and onblocked handles concurrent tabs. Option B is wrong because createIndex is not allowed in a normal readwrite transaction outside the upgrade handler.

    Read the full bite: How do you add a new index to an existing IndexedDB store?

  24. Question 24 of 30

    Which approach correctly and efficiently retrieves IndexedDB products with a price between 50 and 100?

    Show the answer

    Answer: b · Create a price index during onupgradeneeded, then use that index with IDBKeyRange.bound in a readonly transaction.

    IndexedDB schema is version-locked, so the price index must be created in onupgradeneeded, and a readonly transaction with IDBKeyRange.bound efficiently walks only the relevant B-tree leaf nodes. Option C is impossible because createIndex cannot be used outside onupgradeneeded, while option D triggers a full store scan that deserializes every record.

    Read the full bite: How do you query IndexedDB products by price range?

  25. Question 25 of 30

    When returning multi-megabyte IndexedDB query results from a Web Worker to the main thread, what is the primary performance cost?

    Show the answer

    Answer: c · postMessage copies the payload via the Structured Clone Algorithm, adding latency and memory overhead

    postMessage relies on the Structured Clone Algorithm, which copies rather than shares data, so large payloads incur real latency and memory overhead. Option A is tempting but wrong because Transferable Objects are opt-in and not the default behavior for postMessage.

    Read the full bite: Why access IndexedDB exclusively from a Web Worker?

  26. Question 26 of 30

    Which statement correctly distinguishes a Service Worker from a dedicated Web Worker?

    Show the answer

    Answer: d · A Service Worker acts as a network proxy and is started and killed by the browser

    Service Workers are event-driven network proxies the browser starts and terminates at will. Neither worker touches the DOM, dedicated workers serve one page, and request interception is the Service Worker's role.

    Read the full bite: Dedicated vs Shared vs Service Workers compared

  27. Question 27 of 30

    In a news PWA, which resource-to-strategy mapping best balances offline resilience with content freshness?

    Show the answer

    Answer: a · Cache First for the app shell, Stale-While-Revalidate for CSS and JS bundles, and Network First for article APIs

    This mapping matches update frequency and criticality: the versioned app shell must render instantly offline, static assets can load immediately while refreshing in the background, and article APIs should try the network first to avoid serving stale headlines. Option B is tempting because it correctly pairs Cache First with the shell, but it wrongly applies Network First to static assets, which delays returning visitors, and Cache First to APIs, which would present old news as current.

    Read the full bite: Design a Service Worker caching strategy for a news app

  28. Question 28 of 30

    When rendering a mesh in WebGL, why is vertex position data supplied as an attribute while a model-view-projection matrix is supplied as a uniform?

    Show the answer

    Answer: a · The position buffer is bound with vertexAttribPointer so each vertex reads a distinct value, while the MVP matrix is uploaded once per draw call via gl.uniformMatrix4fv and remains constant for all vertices.

    Vertex positions must be attributes because they are fetched from a buffer per vertex via vertexAttribPointer, while an MVP matrix is a uniform set once per draw call with gl.uniformMatrix4fv and shared by all vertices. Option B is tempting but wrong because uniforms cannot vary per vertex within a single draw call, and attempting to update them per vertex from JavaScript would require thousands of individual draw calls.

    Read the full bite: In WebGL, what is the difference between attributes and uniforms?

  29. Question 29 of 30

    After two WebRTC peers complete signaling and connect successfully via a direct path, what role does the signaling server play in the media stream?

    Show the answer

    Answer: a · It plays no role; media flows directly peer-to-peer after setup

    The signaling server only brokers SDP and ICE exchange during setup; once a direct path is found, media flows peer-to-peer. A TURN relay, not the signaling server, handles relaying only when direct connectivity fails.

    Read the full bite: WebRTC signaling: SDP, ICE, and the signaling server

  30. Question 30 of 30

    Why must isolatedModules flag a const enum when a project is transpiled by esbuild?

    Show the answer

    Answer: a · esbuild compiles each file alone and cannot inline the enum's values across files

    const enums rely on the compiler inlining their values throughout the program, which a single-file transpiler cannot do safely. They are not deprecated, and the issue is cross-file inlining, not minification or namespaces.

    Read the full bite: What isolatedModules enforces and why bundlers need it

Could you explain these out loud?

That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon