JavaScript Glossary
Plain-English definitions for the JavaScript terms you keep running into. No guessing, no searching - just clear explanations with examples.
Hoisting is the behavior where JavaScript moves variable and function declarations to the top of their scope before any code runs. You can think of it as JavaScript reading through your code first, registering all the declarations, and only then starting execution.
This happens during the creation phase of the execution context. JavaScript scans your code, finds var declarations and function declarations, and sets them up in memory before the first line executes.
var hoisting
Variables declared with var are hoisted and initialized to undefined. This means you can reference them before their declaration without getting an error - though the value will be undefined.
console.log(score); // undefined (not an error - hoisted to top)
var score = 100;
console.log(score); // 100
Function declaration hoisting
Function declarations are fully hoisted - the entire function body is available from the start of the scope. You can call a function before the line where it is defined.
greet(); // "Hello!" - works because the declaration is fully hoisted
function greet() {
console.log("Hello!");
}
let and const are also hoisted
let and const are hoisted too, but they are not initialized. Accessing them before their declaration line throws a ReferenceError. This gap is called the Temporal Dead Zone.
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = "Alice";
Hoisting is not magic - it is the result of JavaScript's two-phase execution: a creation phase (where declarations are registered) followed by an execution phase (where code actually runs).
To learn more, see the Execution Context tutorial and the Functions Advanced tutorial.
Asynchronous means that a task can start, then be set aside while other code keeps running, and picked up again later when the task is ready. It is the opposite of synchronous (sequential) code where each line must finish before the next one starts.
JavaScript runs on a single thread, meaning it can only do one thing at a time. Asynchronous programming lets that single thread stay responsive while waiting for slow operations - like fetching data from a server or reading a file - to complete in the background.
Synchronous vs Asynchronous
// Synchronous - runs line by line, in order
console.log("1");
console.log("2");
console.log("3");
// Output: 1, 2, 3
// Asynchronous - setTimeout is set aside; other code keeps running
console.log("1");
setTimeout(() => {
console.log("2"); // runs after 2 seconds
}, 2000);
console.log("3");
// Output: 1, 3, 2
How JavaScript handles asynchronous code
The Event Loop is the mechanism that coordinates asynchronous tasks. When an async operation finishes, it is placed in a callback queue. The event loop checks the queue and runs the callback when the call stack is empty.
Common async patterns in JavaScript
- Callbacks - a function passed as an argument to be called when the async task finishes
- Promises - an object representing a value that will be available in the future
- async/await - syntax that makes asynchronous code look and read like synchronous code
// Async/await example
async function loadUser() {
const response = await fetch("/api/user"); // waits here without blocking
const data = await response.json();
console.log(data.name);
}
Asynchronous code lets JavaScript start a slow task (like a network request), move on to other work, and handle the result when it arrives - keeping the page responsive.
To learn more, see the Promises tutorial, the Async/Await tutorial, and the Event Loop tutorial.
The Temporal Dead Zone (TDZ) is the period between entering a scope and the line where a let or const variable is declared. During this period, the variable exists in memory (it has been hoisted) but has not been given a value yet. Trying to access it throws a ReferenceError.
The TDZ is what makes let and const safer than var. Instead of silently returning undefined, JavaScript tells you clearly that the variable is not ready yet.
{}) creates its own scope and its own TDZ for variables declared with let or const.TDZ in action
// TDZ starts here for 'endpoint'
console.log(endpoint); // ReferenceError - still in the TDZ
// TDZ ends here
let endpoint = "/api/users";
console.log(endpoint); // "/api/users" - now safe to use
Comparing var, let, and const
console.log(a); // undefined - var is hoisted and initialized to undefined
// console.log(b); // ReferenceError - let is hoisted but in TDZ
// console.log(c); // ReferenceError - const is hoisted but in TDZ
var a = 1;
let b = 2;
const c = 3;
TDZ in a block scope
let x = "outer";
{
// TDZ for inner 'x' starts here - shadows the outer x
// console.log(x); // ReferenceError, not "outer"
let x = "inner"; // TDZ ends here
console.log(x); // "inner"
}
The Temporal Dead Zone is JavaScript's way of catching mistakes early. If you try to use a let or const variable before declaring it, you get an error immediately - which is much easier to debug than silently getting undefined.
To learn more, see the Variables and Identifiers tutorial, the Scopes tutorial, and the Functions Advanced tutorial.
A shallow copy creates a new object or array and copies all the top-level values into it. However, if any of those values are themselves objects or arrays (called nested values), the copy only copies the reference to that nested value - not the nested value itself. This means the original and the copy share the same nested objects.
string, number, boolean) are always copied by value, so changing them on the copy will not affect the original. Only nested objects and arrays are shared.Shallow copy with spread operator
const original = { theme: "light", lang: "en" };
const copy = { ...original };
copy.theme = "dark";
console.log(original.theme); // "light" - not affected (primitive value)
console.log(copy.theme); // "dark"
This works fine because theme and lang are primitive strings. But watch what happens with a nested object:
const original = { theme: "light", notifications: { email: true } };
const copy = { ...original };
copy.notifications.email = false; // modifying nested object
console.log(original.notifications.email); // false - also changed!
// Both copy and original point to the same notifications object
Common ways to make a shallow copy
- { ...obj } - spread operator for objects
- [...arr] - spread operator for arrays
- Object.assign({}, obj) - assigns own enumerable properties
- arr.slice() - creates a new array with the same elements
To learn more, see the Spread Operator tutorial.
A deep copy creates a completely independent duplicate of an object or array, including all nested objects and arrays. Changing any value in the deep copy - no matter how deeply nested - will not affect the original.
This is the key difference from a shallow copy: a shallow copy shares nested references, while a deep copy duplicates everything at every level.
Deep copy with structuredClone
The modern way to deep copy in JavaScript is structuredClone(), available in all modern browsers and Node.js 17+.
const original = { theme: "light", notifications: { email: true } };
const deepCopy = structuredClone(original);
deepCopy.notifications.email = false;
console.log(original.notifications.email); // true - not affected
console.log(deepCopy.notifications.email); // false
Deep copy with JSON
An older approach is using JSON.parse(JSON.stringify(obj)). It works for plain data objects but has limitations - it drops undefined values, functions, and special types like Date or Map.
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 99;
console.log(original.b.c); // 2 - unchanged
| Shallow Copy | Deep Copy | |
|---|---|---|
| Top-level values | Copied | Copied |
| Nested objects | Shared (same reference) | Duplicated (independent) |
| Mutating nested values | Affects original | Does not affect original |
| Common methods | { ...obj }, Object.assign() |
structuredClone(), JSON.parse/stringify |
Use a shallow copy when your data is flat (no nested objects). Use a deep copy when your data has nested objects and you need a fully independent duplicate. Prefer structuredClone() for deep copies in modern code.
To learn more, see the Spread Operator tutorial and the Objects tutorial.