Unit 2 · Software engineering · Lesson 12 of 15
JavaScript and TypeScript essentials
Step 1: Learn the ideas
- Scope, closures and var versus let
- this binding rules and arrow functions
- Prototypes and classes
- Equality: ==, === and Object.is
- Numbers: IEEE 754, safe integers, NaN, BigInt
- Promises, async/await and the promise combinators
- Structural typing and excess property checks
- Narrowing, discriminated unions and exhaustiveness
- unknown versus any; generics and constraints
- Type erasure and runtime validation at 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
thisorargumentsand cannot be called withnew, which is why they suit callbacks inside methods and fail as object methods that needthis. - "
awaitpauses the program." It suspends only the current async function; other code, including further event handlers, runs meanwhile. - "
Promise.allcancels the rest when one fails." Promises cannot be cancelled; the remaining work continues and its results are ignored. Use anAbortControllerto actually stop requests. - "TypeScript validates API responses." Types vanish at runtime;
as Useron unvalidated JSON is an unchecked claim. - "
constmakes 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.
Suggested order
- 1Read MDN "Closures" and the MDN reference for "this"; predict, then run, ten snippets covering var versus let loops, detached methods, arrow functions and bind.
- 2Read MDN "Inheritance and the prototype chain" and "Equality comparisons and sameness"; write down where class methods live and the result of five == comparisons before running them.
- 3Read the number-encoding part of the MDN Number reference; reproduce precision loss on an ID above 2^53 parsed with JSON.parse, and fix it by transporting the ID as a string.
- 4Read MDN "Using promises" (including the task queues versus microtasks section) and the core MDN JavaScript execution model page; predict the log order of code mixing setTimeout, then and await.
- 5Read the TypeScript Handbook pages Everyday Types, Narrowing, Generics and Type Compatibility; model an API response as a discriminated union handled by an exhaustive switch.
- 6Write a runtime validator for one API response (a schema library or hand-written type guards) and apply it at the fetch boundary so the rest of the code works with trusted types.
- 7Work through the Closures, Function Transformations and Promises and Time sections of LeetCode 30 Days of JavaScript in TypeScript with strict mode on.
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
MDN JavaScript Guide and referenceStart here
MDN Web DocsSection: Closures
Language semantics: closures first, then the this, prototype chain, equality, number encoding and Using promises pages listed under this resource.
Open(opens in a new tab)Link responded, 5 Oct 2026The TypeScript HandbookStart here
Microsoft (typescriptlang.org)Section: Narrowing
Control-flow narrowing, discriminated unions and never-based exhaustiveness; then Everyday Types, Generics and Type Compatibility.
Open(opens in a new tab)Link responded, 5 Oct 2026JavaScript execution model (MDN)
MDN Web DocsThe job queue and run-to-completion model that determine promise and await ordering.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Start with the Closures, Function Transformations and Promises and Time sections of 30 Days of JavaScript; Memoize, Debounce, Promise Time Limit and Execute Asynchronous Functions in Parallel are the most instructive, and solving them in TypeScript forces you to type higher-order functions. Then solve LRU Cache using the insertion order of a Map and Time Based Key-Value Store with a typed binary search. Outside LeetCode: implement a promise pool that runs at most k tasks at once and preserves result order, and a typed event emitter whose listener argument types depend on the event name.
- 30 Days of JavaScript (official LeetCode study plan)LeetCode(opens in a new tab)Not attemptedLeetCode
- LRU CacheLeetCode(opens in a new tab)Not attemptedLeetCode
- Time Based Key-Value StoreLeetCode(opens in a new tab)Not attemptedLeetCode
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1Why do callbacks created in a for loop with var all see the same value?
- Question 2What is this inside a class method passed directly to setTimeout?
- Question 3Where does a class method live, and how is it found from an instance?
- Question 4How does === differ from Object.is?
- Question 5Why is 2 ** 53 + 1 === 2 ** 53 true?
- Question 6What happens to the other operations when Promise.all rejects?
- Question 7Why is unknown safer than any for the result of JSON.parse?
- Question 8What does an as assertion check at runtime?
You’re done when you can
- Predict the output of five unseen snippets mixing closures, this and promise ordering, with at most one mistake.
- Implement debounce and a concurrency-limited promise pool in TypeScript without looking anything up.
- Model a server response as a discriminated union handled by an exhaustive switch that stops compiling when a variant is added.
- Explain type erasure and point to where runtime validation happens in a small app.
Finish the lesson
Unlocks next: Browser runtime: event loop, rendering and promises


