A closure is a function paired with its surrounding lexical environment. It allows an inner function to retain access to the variables of an outer function even after the outer function has returned. Every time a function is created in JavaScript, a closure is formed at function creation time. This mechanism allows functions to “remember” and access their outer scope variables later on, enabling powerful patterns for state management and encapsulation.
Use Cases:
- Data Privacy and State – maintaining a private counter or configuration that only your functions can access.
- Function Factories and Currying – presetting arguments and returning new functions.
- Caching and Memoization – storing previous results in a closure for performance.
- Event Handlers and Async – maintaining state in asynchronous callbacks.
Closures are pervasive in JavaScript – anytime you pass a callback or define a function inside another, you're leveraging closures.
JavaScript has a single-threaded execution model but achieves concurrency through an event loop. Asynchronous operations (timers, network requests, etc.) are offloaded to the environment. When complete, their callbacks are queued and executed once the call stack is empty.
- Macrotasks:
setTimeout
,setInterval
, DOM events, I/O - Microtasks:
Promise.then
,queueMicrotask
,MutationObserver
The event loop executes all microtasks after each task, before the next macrotask.
Behavior Example:
- A
Promise.then()
callback runs before asetTimeout(..., 0)
callback.
This model enables non-blocking I/O and high concurrency in environments like browsers and Node.js.
DOM events go through three phases:
- Capturing phase: from
window
down to the event target. - Target phase: the event reaches the element it was dispatched on.
- Bubbling phase: the event propagates back up the DOM.
Event bubbling allows a parent element to handle events from its children using a single listener.
Advantages:
- Performance: fewer listeners
- Simplicity: easy to handle dynamic elements
Stopping Propagation:
event.stopPropagation()
Frameworks like React use delegation internally.
Hoisting is when variable and function declarations are moved to the top of their scope.
Declaration | Hoisted | Initialized | Notes |
---|---|---|---|
var |
✅ | undefined |
Function or global scope |
let / const |
✅ | ❌ (TDZ) | Block-scoped, ReferenceError if accessed early |
Function Decl. | ✅ | ✅ | Can be called before it's defined |
Function Expr. | Depends | ❌ | Only the variable is hoisted |
TDZ (Temporal Dead Zone): accessing let
or const
before declaration results in an error.
Function expressions and arrow functions follow the hoisting rules of their variable type.
JavaScript is synchronous and single-threaded by design. Only one operation runs at a time on the call stack.
However, asynchronous programming is made possible through:
- Web APIs (DOM, timers, network)
- Callback queues
- Promises / async-await
- The Event Loop
- JS initiates an async operation (e.g.
fetch
). - The task runs in the environment (e.g. browser).
- When complete, the callback is queued.
- The event loop executes it once the stack is clear.
This model powers non-blocking behavior, allowing applications to remain responsive and scalable.
In Practice:
- Browsers: AJAX, user input, rendering
- Node.js: concurrent file/network I/O
JavaScript is single-threaded and synchronous, but asynchronous behavior is enabled through an event-driven architecture and the event loop.