JavaScriptWeb DevelopmentProgramming

The JavaScript Fundamentals That Actually Show Up in Real Code

A practical look at scoping, closures, prototypes, async, and equality — the concepts that explain most everyday Java bugs.

WebPrims Team28 August 20263 min read
The JavaScript Fundamentals That Actually Show Up in Real Code

Variables and Scoping

var is function-scoped. let and const are block-scoped. This single difference explains most of the classic setTimeout loop bug that still appears in production code today.

A var declaration inside a block still attaches to the nearest function scope. So var inside a for loop creates one shared binding, and every iteration sees the same variable. let, by contrast, creates a fresh binding for each iteration, so closures inside the loop behave the way you expect.

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 3, 3, 3
}

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0, 1, 2
}

Prefer const by default. Use let only when you need to rebind. Avoid var entirely in new code.

Closures

A closure is a function that remembers the lexical scope where it was created, even after that scope has returned. That is the whole definition. No magic.

You use closures every time you pass a callback that captures a variable. They are also the simplest way to create private state in JavaScript.

function counter() {
  let count = 0;
  return () => ++count;
}
const next = counter();
next(); // 1
next(); // 2

count is unreachable from anywhere except the returned function. That pattern shows up again and again in real libraries.

The Prototype

Every object in JavaScript has an internal link to another object, its prototype. When you read a property, the engine looks at the object first, then walks up the chain until it finds the property or reaches null.

The class syntax is sugar over this mechanism. The extends keyword wires up the prototype chain explicitly. Understanding this helps you debug unexpected property lookups and explains why Object.create(null) creates a truly bare object.

One common mistake is thinking hasOwnProperty is always available. It exists on the default object prototype, but it is not on objects created via Object.create(null). Use Object.prototype.hasOwnProperty.call(obj, key) when you need a guaranteed check.

Async and the Event Loop

JavaScript runs one thing at a time on the main thread. The event loop handles the rest — timers, network responses, and user events — by queuing tasks and running them in order.

Promises are the clean way to chain async work. An async function always returns a Promise, and await pauses only that function, never the main thread.

async function loadUser(id) {
  const res = await fetch(`/users/${id}`);
  return res.json();
}

The key mental model: await does not block. It suspends the current function and yields back to the loop. Other tasks, like rendering, keep running.

A common mistake is await inside a for loop when you could run tasks in parallel. Use Promise.all when order does not matter and the calls are independent.

Equality

== coerces types before comparing. === does not. The rule of thumb is simple: always use ===.

The one exception worth memorizing is x == null. That check is true for both null and undefined, and it is the idiomatic way to test for either. x === null || x === undefined is equivalent but longer.

Also remember NaN is not equal to itself, even with ===. Use Number.isNaN() instead of comparing to NaN.

Modules

import and export are static, which means the bundler can analyze them at build time and shake out unused code. Named exports and default exports behave differently when it comes to tree shaking — named exports are generally safer for dead-code elimination.

Keep your modules small and focused. A file that exports one function and does one thing is easier to test and reason about than a 500-line utility cabinet.

JavaScriptWeb DevelopmentProgramming

Found this useful?

Share it with someone who is learning.