Tags: javascript, ecmascript, event-loop, promises, prototypes, closures, typescript
Level: intermediate
TL;DR: Core JavaScript execution model, asynchronous mechanics, scope patterns, and object behaviors essential for senior engineering interview technical rounds.
Single-threaded execution runtime powered by the V8 call stack, event loop, and memory heap.
Primitive types pass by value; Objects, arrays, and functions pass by copy of reference.
Lexical scoping: Inner functions retain access to outer variables even after execution contexts pop.
Event loop prioritizes the Microtask queue (Promises, queueMicrotask) over the Macrotask queue (setTimeout, setImmediate).
Prototypal inheritance: Objects delegate missing property lookups up the [[Prototype]] chain to Object.prototype.
Hoisting moves function declarations entirely, whereas var is hoisted as undefined.
let and const remain uninitialized in the Temporal Dead Zone (TDZ) until explicit declaration.
this binding is evaluated at call time based on invocation context, except in arrow functions.
Arrow functions capture this lexically from their enclosing lexical context at declaration time.
Strict equality === checks type and value without triggering implicit coercion.
Q: How does the JavaScript event loop prioritize asynchronous tasks?R: Synchronous code executes first on the call stack. Once emptied, the event loop drains the entire microtask queue before picking a single macrotask.
Q: What is the difference between == and ===?R: == triggers abstract type coercion before comparison. === checks both value and type directly without conversion, preventing unexpected truthy evaluations.
Q: How do closures work and what memory implications do they have?R: Closures retain references to their outer lexical environment. If a long-lived closure references large unused variables, they cannot be garbage collected.
Q: What is the difference between prototypal and classical inheritance?R: Prototypal inheritance delegates behavior dynamically between objects via prototype chains, whereas classical inheritance relies on rigid static blueprints copied at instantiation.
Implicit type coercion using + operator concatenating strings instead of performing numeric additions.
Mutating shared object references across utility functions instead of creating shallow or deep clones.
Forgetting that typeof null returns "object" due to legacy 32-bit tag representation.
Unhandled Promise rejections terminating Node.js runtime processes or failing silently in browsers.
Calling bind, call, or apply on arrow functions expecting to alter their contextual this.
Relying on setTimeout(fn, 0) for immediate execution instead of recognizing microtask queue prioritization.
Modifying arrays during loop iterations without indexing adjustment or functional pipelines.
// Microtask vs Macrotask execution order
console.log('1: Sync');
setTimeout(() => console.log('4: Macrotask'), 0);
Promise.resolve().then(() => console.log('3: Microtask'));
console.log('2: Sync');
// Output: 1, 2, 3, 4
TypeScript
// Closure implementation for encapsulation
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment: () => ++count,
getValue: () => count
};
}
TypeScript
// Polyfill style shallow copy avoiding prototype alteration
const shallowClone = <T extends object>(target: T): T => Object.assign({}, target);
[ ] Garbage collection heuristics and weak references (WeakMap, WeakSet)
[ ] Generator execution suspension and async iterator protocols
[ ] Proper error propagation using Promise.allSettled vs Promise.all
[x] Event loop microtask and macrotask execution order
[x] Variable scope boundaries and Temporal Dead Zone rules
MDN Web Docs JavaScript Guide
ECMAScript Language Specification (ECMA-262)