interviewpath
  • 0day streak
  • 10total XP
  • Level 1

Focus mode

JavaScript and TypeScript essentials

Read alongside

MDN JavaScript Guide and reference: Closures

Language semantics: closures first, then the this, prototype chain, equality, number encoding and Using promises pages listed under this resource.

Objectives

  • Explain closures and predict variable capture with var versus let in loops and callbacks.
  • Determine this for method calls, plain calls, detached callbacks, arrow functions, bind and new.
  • Explain prototype-based property lookup and how class syntax maps onto it.
  • Compare ==, === and Object.is, and explain number pitfalls: floating-point error, the safe-integer range, NaN, bitwise truncation and BigInt.
  • Predict promise and async/await ordering, and choose between sequential awaits, Promise.all, allSettled, race and any with correct error handling.
  • Use structural typing, narrowing (typeof, in, discriminated unions, type predicates) and generics, and explain why unknown is safer than any.
  • Explain type erasure and validate untrusted data such as JSON responses and form input at runtime boundaries.

Why it matters

Many frontend bugs come from language semantics rather than frameworks: a handler that loses this, a stale closure, a 64-bit ID rounded during JSON parsing, a rejected promise nobody awaited. TypeScript prevents many of them, but only inside the compiler's view of the program: data from the network, storage or user input arrives untyped whatever the annotations claim. Interviews test both halves: predicting what the runtime does, and choosing types that make invalid states hard to represent.

Core ideas

Closures. A function keeps access to the variables in scope where it was created, not to snapshots of their values. let and const create a fresh binding per loop iteration; var is function-scoped, so callbacks created in a var loop all see the final value. Closures implement private state, memoization and debouncing, and they cause stale-value bugs when a callback holds on to an old variable.

this. For ordinary functions the call decides this: obj.f() binds obj; a plain call f() gets undefined in strict code (modules and class bodies are strict); new binds the newly created object; call, apply and bind set it explicitly. Arrow functions have no this of their own and use the enclosing one. Passing obj.method as a callback detaches it from obj.

Prototypes. Property lookup walks the prototype chain until it finds the key or reaches null. class syntax builds the same structure: methods live on Class.prototype and are shared by all instances, with stricter rules such as requiring new.

Equality and numbers. === compares without type conversion; == coerces, so null == undefined, '' == 0 and [] == false are all true. Object.is differs from === only for NaN (equal to itself) and for +0 versus -0. Objects compare by reference. Every number is an IEEE 754 double: 0.1 + 0.2 !== 0.3, integers are exact only up to Number.MAX_SAFE_INTEGER (2^53 − 1), and bitwise operators convert their operands to 32-bit integers. Send 64-bit IDs as strings and use BigInt when exact large integers matter.

Promises and async/await. A promise settles once. Callbacks registered with then, and the code after an await, always run later as microtasks, even when the promise has already settled. An async function always returns a promise, and a throw inside it becomes a rejection. Sequential awaits in a loop serialize independent work; Promise.all runs it concurrently but rejects on the first failure while the other operations keep running; allSettled collects every outcome; race settles with the first promise to settle and any fulfills with the first to fulfill. array.forEach(async ...) waits for nothing.

TypeScript's model. Compatibility is structural: a value fits a type if it has the required members, whatever its declared name. Fresh object literals additionally get excess-property checks. Narrowing refines a union through control flow with typeof, instanceof, in, equality checks, discriminant fields and user-defined type predicates; assigning the leftover case to never makes a switch exhaustive. any turns checking off and spreads silently; unknown forces you to narrow before use. Generics express relationships between inputs and outputs, and constraints such as T extends { id: string } state what the body may assume.

Erasure. The compiler removes types: as assertions and type parameters perform no runtime checks. JSON.parse and response.json() return any, so validate external data where it enters, with a schema library or hand-written type guards, and keep trusted types inside.

type Result = { ok: true; value: number } | { ok: false; error: string };
function parse(raw: unknown): Result {
  if (typeof raw === 'object' && raw !== null && 'value' in raw && typeof raw.value === 'number') {
    return { ok: true, value: raw.value };
  }
  return { ok: false, error: 'invalid payload' };
}

Common misconceptions

  • "Arrow functions are just shorter functions." They have no own this or arguments and cannot be called with new, which is why they suit callbacks inside methods and fail as object methods that need this.
  • "await pauses the program." It suspends only the current async function; other code, including further event handlers, runs meanwhile.
  • "Promise.all cancels the rest when one fails." Promises cannot be cancelled; the remaining work continues and its results are ignored. Use an AbortController to actually stop requests.
  • "TypeScript validates API responses." Types vanish at runtime; as User on unvalidated JSON is an unchecked claim.
  • "const makes a value immutable." It prevents rebinding the name; the object itself can still be mutated.

How to approach it

Predict-then-run small snippets in the browser console or Node for each idea. Graduate interviews tend to check closures, this, equality and async ordering; mid-level and senior frontend interviews add typing an API client, modeling UI state with discriminated unions and explaining what the type system can and cannot guarantee. Implementing debounce, memoize, a promise pool with a concurrency limit and a typed event emitter covers most language-level practice.

Self-check before you finish

  1. Why do callbacks created in a for loop with var all see the same value?
  2. What is this inside a class method passed directly to setTimeout?
  3. Where does a class method live, and how is it found from an instance?
  4. How does === differ from Object.is?
  5. Why is 2 ** 53 + 1 === 2 ** 53 true?
  6. What happens to the other operations when Promise.all rejects?
  7. Why is unknown safer than any for the result of JSON.parse?
  8. What does an as assertion check at runtime?