I used to "learn" the same JavaScript concepts every few months. I'd read about the event loop, nod along, and a month later get a predict-the-output question wrong in an interview.
What finally worked was seeing the rule every day. I turned each concept into a phone wallpaper with a short why at the bottom, so every time I unlock my phone, I revise one thing.
Here are the five that helped most. Try to predict each output before reading the answer.
1. The event loop: why promises beat setTimeout
setTimeout(() => console.log('C')); Promise.resolve().then(() => console.log('B')); console.log('A'); Output: A, B, C
The rule that made it stick:
- Run all synchronous code on the call stack.
- When the stack is empty, drain every microtask (promise callbacks, code after
await,queueMicrotask). - Then take one macrotask (
setTimeout, I/O, clicks) and repeat.
So a promise callback always runs before setTimeout(fn, 0), even though both look "async". The 0 doesn't mean "now"; it means "next macrotask".
2. Closures: functions remember where they were born
function counter() { let count = 0; return () => ++count; } const next = counter(); next(); next(); console.log(next()); Output: 3
The returned function keeps a live reference to count, not a copy. That's a closure: a function plus the variables that were in scope when it was created.
The classic interview trap is the same idea:
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i)); } // 3, 3, 3 All three callbacks close over the same var i, which is 3 by the time they run. Change var to let and you get 0, 1, 2, because let creates a new binding for each loop iteration.
3. this: ask how the function was called
const user = { name: 'Asha', greet() { return `Hi, ${this.name}`; }, }; const greet = user.greet; console.log(greet()); Output: in an ES module or strict mode, TypeError: Cannot read properties of undefined (reading 'name'). In an old-style non-strict script, this falls back to the global object, so you get a wrong name instead of an error. Either way, it's not Hi, Asha.
this isn't decided where a function is written; it's decided by how it's called. Check these in order:
- Arrow function: never gets its own
this; uses the one from the surrounding code. -
new Fn():thisis the brand-new object. -
fn.call(obj)/apply/bind:thisisobj. -
obj.fn():thisis whatever is left of the dot. - Plain
fn():undefinedin strict mode.
greet() has no dot at call time, so it lost user.
4. Type coercion: + and - play by different rules
Cover the answers and predict:
[] + {} // ? "5" - 2 // ? "5" + 2 // ? null == 0 // ? null >= 0 // ? Answers: "[object Object]", 3, "52", false, true
-
+switches to string concatenation if either side ends up a string.[]becomes""and{}becomes"[object Object]". -
-has no string version, so it always converts to numbers. -
null >= 0convertsnullto0, so0 >= 0istrue. But==has a special rule:nullonly equalsundefined. That's hownullcan be>= 0without being== 0.
5. Hoisting: let is hoisted too, just locked
console.log(a); // ? var a = 1; console.log(b); // ? let b = 2; Output: undefined, then ReferenceError: Cannot access 'b' before initialization
Both declarations are hoisted. The difference:
-
varis hoisted and initialized toundefined. -
let,constandclassare hoisted but sit in the temporal dead zone until their line runs. - Function declarations are hoisted completely, so you can call them before they appear.
The error is a feature: it catches bugs that var would silently hide as undefined.
Want them on your lock screen?
I run TechCookies, a small platform built around this "predict first, then learn the why" style. These wallpapers came out of it, and there are 26 in total: JavaScript, TypeScript, SQL, DSA, system design, Java and Python.
Tip for iPhone: put them in one album and use Photo Shuffle on the lock screen, so you get a new concept every day.
Which JavaScript concept took you the longest to really understand? I'm making more wallpapers and would like to cover the ones people actually struggle with.