Learn Frontend Part 5: JavaScript Fundamentals - Types, Scope, Hoisting and Closures

Learn Frontend Part 5: JavaScript Fundamentals - CodingHumans

JavaScript interviews love questions that look trivial but test deep understanding: why does [] == false return true, why does a var loop print the same number three times, and what exactly is a closure? All of them come from a handful of fundamentals: types, equality, scope, hoisting and closures.

This is Part 5 of the Learn Frontend series. We cover these core concepts, plus the array methods and modern syntax you will use every day in React code.

1. Types: primitives vs references

JavaScript has seven primitive types: string, number, bigint, boolean, undefined, null and symbol. Everything else (objects, arrays, functions, dates) is an object.

  • Primitives are copied by value and are immutable.
  • Objects are handled by reference: assigning an object to another variable copies the reference, so both variables point to the same object. Mutating through one is visible through the other. This is exactly why React state must be updated immutably (Part 11).
  • typeof null is "object", a historical bug. Use Array.isArray() to detect arrays.
  • Copying: {...obj} and [...arr] make shallow copies; structuredClone(obj) makes a deep copy.

2. Equality and type coercion

=== (strict equality) compares type and value with no conversion. == (loose equality) converts types first, producing surprises such as 0 == '', null == undefined and [] == false all being true.

  • Use === almost always. The one common exception is x == null, which checks for both null and undefined.
  • Falsy values: false, 0, -0, 0n, '', null, undefined, NaN. Everything else is truthy, including [], {} and "0".
  • NaN !== NaN; use Number.isNaN().
  • ?? (nullish coalescing) only falls back for null or undefined, unlike ||, which also replaces 0 and empty strings.

3. Scope, var, let and const, and hoisting

varletconst
ScopeFunctionBlockBlock
HoistedYes, initialised to undefinedYes, but in the temporal dead zoneYes, but in the temporal dead zone
Re-assignableYesYesNo (but object contents can change)

Hoisting means declarations are processed before code runs. var variables exist from the start of the function with the value undefined; let and const exist but cannot be touched until their declaration line (the temporal dead zone), so accessing them early throws a ReferenceError. Function declarations are hoisted completely, so you can call them before they appear; function expressions and arrow functions are not.

console.log(a);   // undefined   (var is hoisted and initialised)
var a = 1;

console.log(b);   // ReferenceError: Cannot access 'b' before initialization
let b = 2;        // b was in the temporal dead zone

greet();          // works: function declarations are fully hoisted
function greet() { console.log('hi'); }

JavaScript uses lexical scope: a function can see variables from where it was written, not from where it is called. Inner scopes can read outer variables, forming a scope chain.

4. Closures

A closure is a function together with the variables from the scope where it was created. The inner function keeps access to those variables even after the outer function has returned. Closures power private state, factories, memoization, event handlers and React hooks.

function makeCounter() {
  let count = 0;                  // private: only the closure can reach it
  return {
    increment: () => ++count,
    get: () => count,
  };
}
const c = makeCounter();
c.increment(); c.increment();
console.log(c.get());             // 2

// Classic interview question
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));  // 3 3 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j));  // 0 1 2

The classic loop bug happens because var creates one shared variable for the whole loop, so every callback sees its final value. let creates a fresh binding for each iteration, so each callback closes over its own value.

Stale closures in React: a callback created during one render "remembers" that render's state. If it runs later (in a timer or effect), it may see old values. Part 11 shows how to avoid this.

5. Functions and array methods

Functions are values: you can pass them as arguments and return them from other functions (higher-order functions). The array methods built on this are used constantly in frontend code.

const orders = [
  { id: 1, amount: 499,  status: 'paid' },
  { id: 2, amount: 1299, status: 'pending' },
  { id: 3, amount: 899,  status: 'paid' },
];

const paidTotal = orders
  .filter(o => o.status === 'paid')       // keep paid orders
  .map(o => o.amount)                      // take amounts
  .reduce((sum, x) => sum + x, 0);         // add them up
console.log(paidTotal);                    // 1398

const byStatus = Object.groupBy(orders, o => o.status);  // { paid: [...], pending: [...] }
  • map, filter and reduce return new values and do not mutate the original array, which suits React.
  • forEach returns nothing; use it only for side effects.
  • sort, reverse and splice mutate in place; toSorted, toReversed and toSpliced are the non-mutating versions.
  • find, some, every and includes answer common questions without loops.

6. Modern syntax you will use daily

  • Destructuring: const { name, age = 18 } = user; and const [first, ...rest] = list;
  • Spread and rest: {...user, age: 30} copies and overrides; function sum(...nums) collects arguments.
  • Optional chaining: user?.address?.city returns undefined instead of throwing.
  • Template literals: `Hello ${name}`.
  • Arrow functions: shorter syntax and no own this (Part 6 explains why that matters).
  • Modules: import and export split code into files; bundlers combine them (Part 15).
  • Map and Set: keyed collections with any key type, and unique values.
Key takeaways
  • Primitives are copied by value; objects are shared by reference, so update them immutably.
  • Prefer === ; know the falsy values; use ?? when 0 or empty string are valid.
  • var is function-scoped and hoisted as undefined; let and const are block-scoped with a temporal dead zone.
  • A closure keeps access to its creation scope; it explains private state, the var loop bug and stale React state.
  • Use map, filter and reduce for transformations, and modern syntax like destructuring, spread and optional chaining.

Practice problems

Predict the output first, then run each snippet in the browser console to check.

  1. Predict: console.log(typeof null, typeof [], typeof function(){}).
  2. Predict: console.log(0 == '', null == undefined, NaN === NaN, [] == false).
  3. Write a once(fn) function that lets fn run only the first time it is called.
  4. Write a memoize(fn) function using a closure and a Map.
  5. Fix the var-in-a-loop setTimeout bug in two different ways.
  6. Implement your own map and filter on Array.prototype (as myMap, myFilter).
  7. Write a deep clone function, then compare it with structuredClone.

Interview questions and FAQ

What is a closure in JavaScript?

A closure is a function bundled with references to the variables in the scope where it was defined. It can still read and update those variables after the outer function returns, which enables private state, factories and callbacks.

What is the difference between var, let and const?

var is function-scoped and hoisted with the value undefined. let and const are block-scoped and stay in the temporal dead zone until declared. const cannot be reassigned, although objects it points to can still be mutated.

What is hoisting?

JavaScript processes declarations before running code. var variables become available as undefined, function declarations become fully usable, and let, const and class are hoisted but cannot be accessed before their declaration line.

What is the difference between == and ===?

=== compares value and type without conversion. == converts operands to a common type first, which leads to confusing results. Use === except for the deliberate x == null check.

What is the difference between null and undefined?

undefined means a variable has been declared but not given a value, or a property does not exist. null is an intentional "no value" that you assign yourself.

Comments

Answered 0/0Correct 0