JavaScript Concepts Explained With Examples That Click
Closures, the event loop, this and hoisting trip up most learners. Here are the four JavaScript concepts that make sense once you see the right example.

6 min read · Last updated: October 25, 2026
The JavaScript concepts that confuse beginners most are closures, the event loop, this and hoisting. Each follows a short rule: a function remembers where it was written, one job runs at a time, this is set by how you call a function, and declarations are registered before code runs.
Came here from Instagram, LinkedIn, X or YouTube? Here is the full source list you came for. Everything below is built from these, nothing else.
The docs (MDN Web Docs, all read October 25, 2026)
Key takeaways
- A closure is a function bundled with references to its surrounding state. MDN says closures are created every time a function is created.
- JavaScript processes one job at a time, and each job is processed completely before the next starts.
- Microtasks (promise callbacks) always run before timer tasks such as
setTimeout, even with a 0 ms delay. - The value of
thisdepends on how a function is called, not where it is defined. Arrow functions are the exception. varis readable before its line asundefined.letandconstthrow a ReferenceError (the temporal dead zone).
What do these JavaScript concepts have in common?
They all describe when things happen and what a function can see. Once you know the single rule behind each, the confusing output stops being random.
Picture a one-person coffee shop. One barista takes one order at a time and finishes it before starting the next. That is the event loop. Each order slip is a job. A function that carries a small backpack of notes about where it came from is a closure. The rest of this post walks through four concepts, each with the rule and a snippet you can paste into a console.

What is a closure in JavaScript?
A closure is a function bundled together with references to its surrounding state, called its lexical environment. In plain terms, an inner function can still use the variables of the function that created it, even after that outer function has finished running.
MDN defines it this way: "A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope." Here is MDN's own example:
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
How it works, step by step:
makeAdder(5)runs and creates a variablexset to 5.- It returns the inner function. That function keeps a reference to
x. makeAdder(10)runs separately and creates its ownx.- Each returned function has its own backpack, so
add5andadd10never interfere.
Why does a loop with var break closures?
With var, every closure created in a loop shares one variable. MDN explains that all the callbacks share "a single lexical environment", and by the time they run, the loop has finished, so the variable points at the last entry. Declaring the variable with let or const inside the loop gives each iteration its own binding.
How does the JavaScript event loop work?
The event loop lets single-threaded JavaScript handle async work. MDN states that "an agent is a thread, which means the interpreter can only process one statement at a time." Finished async actions place callbacks in a queue, and the agent pulls one job at a time and runs it fully.
The steps:
- Your synchronous code runs first, pushing and popping frames on the call stack.
- When a timer, I/O or event completes, its callback becomes a job in a queue.
- MDN says that "each job is processed completely before any other job is processed."
- Microtasks have higher priority. Per MDN, the microtask queue is drained first, before the task queue is pulled.
MDN's microtask guide confirms that promises use the microtask queue, and that a setTimeout callback runs after a queued microtask. This snippet shows the order (the promise-and-timer version is a standard illustration of the same rule, not copied from MDN):
console.log("Sync start");
setTimeout(() => console.log("setTimeout callback"), 0);
Promise.resolve().then(() => console.log("Promise then callback"));
console.log("Sync end");
// Sync start
// Sync end
// Promise then callback
// setTimeout callback
So setTimeout(fn, 0) does not mean "now". It means "as a later task, after the current job and the microtasks".
| Step | What runs | Queue it came from |
|---|---|---|
| 1 | Sync start, Sync end | Call stack (current job) |
| 2 | Promise then callback | Microtask queue |
| 3 | setTimeout callback | Task queue |
The cost of this model, in MDN's words: "if a job takes too long to complete, the web application is unable to process user interactions like click or scroll." Its advice is to keep jobs short and split big ones into several.
What does this mean in JavaScript?
this is set by how a function is called, not by where it is written. MDN says: "The value of this in JavaScript depends on how a function is invoked (runtime binding), not how it is defined." For obj.f(), this is obj. For a plain call in strict mode, it is undefined.
Use this table as your decision rule:
| How the function is called | Value of this (per MDN) |
|---|---|
obj.f() | obj, the object used to call it |
f() in strict mode | undefined |
f() in non-strict mode | globalThis |
f.call(o) or f.apply(o) | o, for that one call |
f.bind(o) | o, fixed for the new function |
| Arrow function | this of the surrounding scope |
MDN also notes that arrow functions ignore call(), bind() and apply() for this, and that class code is strict, so a method detached from its object gets this === undefined. A small example of the classic bug:
"use strict";
const user = {
name: "Ada",
greet() {
return this.name;
},
};
console.log(user.greet()); // "Ada"
const greet = user.greet;
greet(); // TypeError: this is undefined in strict mode
const bound = user.greet.bind(user);
console.log(bound()); // "Ada"
The function is identical in all three calls. Only the call site changed.
What is hoisting in JavaScript?
Hoisting is the interpreter appearing to move declarations of functions, variables, classes or imports to the top of their scope before the code runs. MDN notes it is not a term formally defined in the ECMAScript specification. What matters is how each declaration behaves before its line.
| Declaration | Before its line |
|---|---|
function | Callable and usable |
var | Readable, value is undefined |
let, const, class | ReferenceError (temporal dead zone) |
greet(); // works
function greet() {
console.log("Hello");
}
console.log(a); // undefined
var a = 5;
MDN's trickiest example shows that let and const still affect their whole block:
const x = 1;
{
console.log(x); // ReferenceError
const x = 2;
}
The inner const x "taints" the entire block, so the log reads the uninitialised inner x instead of the outer one.
When should you use each concept, and when not?
These are not tools you pick. They are behaviours you work with. The practical question is which habit each one should change.
| Concept | Lean on it when | Watch out when |
|---|---|---|
| Closures | You need private state or a function factory (makeAdder, counters) | Creating callbacks in a var loop |
| Event loop | You split heavy work into short jobs to keep the page responsive | A long synchronous job blocks clicks and scrolling |
this | You call methods as obj.method() or use bind deliberately | You pass a method as a callback and lose its receiver |
| Hoisting | You declare functions at the top level and call them anywhere | You read a let or const before its line |
MDN even suggests it may be more useful to treat let, const and class as non-hoisting, since hoisting gives them no useful feature. Declare before you use and you rarely meet any of this.
FAQ
Why does setTimeout with 0 delay run after my promise?
Promise callbacks go in the microtask queue, which MDN says is drained before the task queue is pulled. A setTimeout callback is a task, so it waits until the current job finishes and every queued microtask has run.
Is a closure the same as a nested function?
No. A nested function becomes a closure when it keeps access to its outer scope's variables. MDN says closures are created every time a function is created, but the effect is most visible when an inner function outlives its outer function, as in makeAdder.
Why is this undefined in my callback?
Because this depends on the call, and a callback is called by someone else. In strict mode a plain call gives undefined. Use an arrow function, which takes this from its surroundings, or bind the method first.
Do I need to learn all four before building projects?
You can start building now and learn each rule when you hit it. If you are still choosing a path, read Python vs JavaScript: which to learn first and the AI-first roadmap to learn programming.
Paste each snippet into your browser console, change one line, and predict the output before you run it. Then browse the free tools on Art of Code and the 10 JavaScript one-liners worth knowing.
Written by Adithya Guttha, Founder of Art of Code.
Sign in to like and save posts.
Keep reading



Discussion
Comments (0)
Want to join the conversation?
Sign in to commentNo comments yet. Be the first to comment!