JavaScript & TypeScript Mastery
JavaScript Engine Internals
Understanding how JavaScript executes code is the foundation of every frontend interview. These concepts appear in coding rounds, trivia questions, and debugging scenarios.
The Event Loop
JavaScript is single-threaded but non-blocking, thanks to the event loop. One turn of that loop looks like this:
One turn of the event loop
The ordering question in every phone screen comes from this cycle. Note where rendering sits: it is not a queued task competing with your callbacks, it is a phase the browser reaches once per turn — which is why a long task blocks paint as well as input.
Critical rule: the microtask queue is drained completely before the next macrotask runs — and it is drained again after each macrotask, not once per loop.
An infinite chain of microtasks therefore starves the page permanently: each one queues the next, the drain never finishes, and the browser never reaches its rendering phase. An infinite chain of setTimeout(fn, 0) calls does not, because each callback is a separate macrotask with a checkpoint between them. This distinction is the follow-up question when a candidate answers the ordering question correctly.
// Classic interview question: predict the output order
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
// Why: 1 and 4 are synchronous (call stack).
// Promise.then is a microtask (runs before setTimeout).
// setTimeout is a macrotask (runs last).
Closures
A closure is a function that retains access to its lexical scope even after the outer function has returned.
// Interview classic: the loop closure problem
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (var is function-scoped, not block-scoped)
// Fix 1: use let (block-scoped)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
// Fix 2: IIFE closure
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 100);
})(i);
}
// Output: 0, 1, 2
Practical closure pattern — memoization:
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveCalc = memoize((n) => {
console.log('Computing...');
return n * n;
});
expensiveCalc(5); // Computing... 25
expensiveCalc(5); // 25 (cached, no log)
The this Keyword
JavaScript has four rules for this binding, applied in this priority order:
| Rule | When | this is |
|---|---|---|
1. new binding | new Foo() | The newly created object |
| 2. Explicit binding | call(), apply(), bind() | The specified object |
| 3. Implicit binding | obj.method() | The object before the dot |
| 4. Default binding | Plain function call | undefined (strict) or globalThis |
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
},
greetLater() {
// Arrow functions inherit `this` from enclosing scope
setTimeout(() => console.log(this.name), 100);
}
};
obj.greet(); // 'Alice' (implicit binding)
obj.greetLater(); // 'Alice' (arrow fn inherits `this`)
const fn = obj.greet;
fn(); // undefined (default binding, lost context)
Interview tip: Arrow functions do NOT have their own
this. They inheritthisfrom the enclosing lexical scope. This is why arrow functions are preferred in callbacks and event handlers inside class methods or React components.
Prototypal Inheritance
Every JavaScript object has an internal [[Prototype]] link. Property lookups follow the prototype chain:
const animal = {
speak() {
return `${this.name} makes a sound`;
}
};
const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak(); // 'Rex makes a sound' (found via prototype chain)
// ES6 class syntax is syntactic sugar over prototypes
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
bark() {
return `${this.name} barks`;
}
}
WeakMap and WeakSet
These are less commonly known but appear in senior interviews:
// WeakMap: keys must be objects, and are garbage-collected
// when no other references exist
const metadata = new WeakMap();
function process(obj) {
if (!metadata.has(obj)) {
metadata.set(obj, { processedAt: Date.now() });
}
return metadata.get(obj);
}
// Use case: attaching data to DOM elements without memory leaks
// When the element is removed from DOM, the WeakMap entry
// is automatically garbage-collected
| Feature | Map | WeakMap |
|---|---|---|
| Key types | Any | Objects only |
| Enumerable | Yes (forEach, keys()) | No |
| Garbage collection | Prevents GC of keys | Allows GC of keys |
| Use case | General-purpose | Metadata, caching without leaks |
Next, we'll cover TypeScript patterns that appear frequently in frontend interviews. :::
Sign in to rate