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. Here is the execution model:
┌─────────────────────────────────┐
│ Call Stack │ ← Synchronous code executes here
│ (one function at a time) │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Microtask Queue │ ← Promise callbacks, queueMicrotask()
│ (drains completely before │ MutationObserver
│ moving to macrotasks) │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Macrotask Queue │ ← setTimeout, setInterval,
│ (one task per loop iteration) │ I/O callbacks, UI rendering
└─────────────────────────────────┘
Critical rule: The microtask queue is drained completely before the next macrotask runs.
// 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. :::