A not-defined ReferenceError means identifier resolution found no usable binding at that point. Diagnose lexical scope, temporal dead zone, spelling and path case, import shape, module evaluation, script loading, and deployed asset state separately from undefined property values.
Module-aware linting, explicit integration adapters, cycle removal, production-build smoke tests, and release-matched source maps prevent and localize these failures.
ReferenceError occurs when you try to use a variable that hasn't been declared or is not in the current scope. This is one of the most common JavaScript errors, especially for beginners.
// [wrong] Problem
console.log(userName); // ReferenceError: userName is not defined
// [ok] Solution 1: Declare the variable first
let userName = 'John';
console.log(userName); // 'John'
// [ok] Solution 2: Check if variable exists
if (typeof userName !== 'undefined') {
console.log(userName);
}
// [ok] Solution 3: Use try-catch
try {
console.log(userName);
} catch (error) {
console.log('Variable not defined');
}
console.log(age); // ReferenceError: age is not defined
age = 25; // This won't help - error already thrown
// Declare before using
let age = 25;
console.log(age); // 25
// Or use var (hoisted but undefined initially)
console.log(age); // undefined (no error)
var age = 25;
let userName = 'John';
console.log(username); // ReferenceError: username is not defined
// Note: userName vs username (capital N)
let userName = 'John';
console.log(userName); // 'John' - correct spelling
// Use consistent naming conventions
// camelCase for variables
let firstName = 'John';
let lastName = 'Doe';
if (true) {
let message = 'Hello';
}
console.log(message); // ReferenceError: message is not defined
// Same with loops
for (let i = 0; i < 5; i++) {
// i is only available here
}
console.log(i); // ReferenceError: i is not defined
// Solution 1: Declare outside the block
let message;
if (true) {
message = 'Hello';
}
console.log(message); // 'Hello'
// Solution 2: Use var (function-scoped, not recommended)
if (true) {
var message = 'Hello';
}
console.log(message); // 'Hello' (works but not recommended)
// Solution 3: Return value from block
function getMessage() {
if (true) {
let message = 'Hello';
return message;
}
}
console.log(getMessage()); // 'Hello'
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = 'John';
// Same with const
console.log(PI); // ReferenceError
const PI = 3.14;
// Always declare variables at the top
let name = 'John';
console.log(name); // 'John'
const PI = 3.14;
console.log(PI); // 3.14
// Or use var (hoisted, but not recommended)
console.log(name); // undefined (no error)
var name = 'John';
// main.js
const result = calculateSum(5, 10); // ReferenceError: calculateSum is not defined
// utils.js (separate file)
export function calculateSum(a, b) {
return a + b;
}
// main.js - Import the function
import { calculateSum } from './utils.js';
const result = calculateSum(5, 10); // 15
// Or import everything
import * as utils from './utils.js';
const result = utils.calculateSum(5, 10);
A ReferenceError that an identifier is not defined means name resolution could not find a binding available at that point. This differs from a declared binding whose value is undefined and from an existing object whose property is missing. Inspect the bare identifier and lexical scopes before adding a value check.
JavaScript searches the current lexical environment and enclosing environments according to scope rules. Blocks scope `let`, `const`, and class declarations; functions create function scope; modules create module scope. A similarly named global does not repair a missing local import or typo.
`typeof missingName` returns `"undefined"` for a truly undeclared identifier in ordinary cases, but using typeof on a lexical binding in its temporal dead zone throws. Do not use typeof as a general initialization probe for local code. Declare and initialize dependencies explicitly.
In strict mode, assigning to an undeclared name throws instead of creating an accidental global. This is a useful failure. Fix the declaration or import rather than attaching random properties to globalThis, which hides ownership and creates load-order coupling.
`let`, `const`, and class bindings exist from the start of their scope but remain uninitialized until evaluation reaches the declaration. Access before initialization throws a ReferenceError. The temporal dead zone makes ordering defects visible instead of returning a misleading undefined value.
An inner declaration shadows an outer binding throughout the inner scope, including before the inner declaration is initialized. Code cannot fall back to the outer name during that interval. Rename or move the inner declaration when shadowing hides the intended binding.
Default parameter initializers run before the function body in their own environment. A default can use an earlier parameter but cannot safely depend on a later uninitialized parameter or a body declaration. Keep defaults simple and compute interdependent values after function entry.
Class declarations also have temporal dead zone behavior, and derived constructors cannot use `this` before `super()`. Although error messages vary, diagnose the exact binding and initialization phase rather than grouping every ReferenceError as a missing script.
An imported binding must match an exported name and module path. Default and named imports are different contracts. A failed module fetch or parse prevents dependent evaluation, while a typo can be caught at link time. Inspect the first module error rather than only the later application symptom.
Classic scripts share a global environment and can depend on document order, defer, async, or dynamic insertion. `async` scripts execute when downloaded and do not preserve source order. Modules have dependency-based evaluation and deferred behavior by default, but cyclic imports can expose bindings before initialization.
Break module cycles by moving shared types or utilities to a lower-level module, passing dependencies into factories, or creating an explicit bootstrap phase. Reordering imports without understanding the graph may only move the temporal dead zone failure.
Globals supplied by optional third-party scripts need a documented loading and failure contract. Prefer module imports or a small adapter that validates the expected global once. Network failure, blockers, consent state, and incompatible versions must produce a controlled unavailable path.
Pause on the exception and inspect the scope panel, exact spelling and case, source URL, execution order, and module graph. Search declarations and imports with case-sensitive tooling. On case-insensitive development file systems, a path case mismatch may surface only after deployment.
Check whether the production bundle omitted a chunk, renamed a global, loaded an HTML error response as script, or used a stale cached entry point. Source maps and asset manifests must match the deployed release. Clearing a local cache is evidence gathering, not a production repair.
Lint undeclared identifiers, shadowing, inconsistent returns, and unresolved imports. Type checking and module-aware editors catch many names before runtime. Keep environment globals explicitly configured so a misspelling is not accepted merely because every unknown name is allowed.
Regression tests should load the real entry graph, optional integrations, lazy routes, workers, and production build. Include failure of a third-party script and a cyclic-dependency fixture where relevant. Assert the intended unavailable or error state, not only the absence of a ReferenceError.
A browser page, worker, service worker, Node.js module, test runner, and embedded web view expose different global bindings. Use `globalThis` only for a capability that is intentionally global across supported hosts, and feature-detect the exact API before use. Do not assume `window`, `self`, or process-specific names exist everywhere.
Test environments may inject globals that production does not provide, while bundlers can replace environment constants at build time. Declare those contracts in build and type configuration and run at least one test against the real target. A test-only global can make missing imports invisible.
Content Security Policy, sandboxing, permissions, and extension isolation can make a normally available integration inaccessible. Distinguish a missing identifier from a defined API that rejects access. The former requires loading or code repair; the latter requires a permission or fallback path.
// Wrong: JavaScript names are case-sensitive.
const userName = "Asha";
try {
console.log(username);
} catch (error) {
console.log(error.name); // ReferenceError
}
// Fix: use the declared identifier.
console.log(userName); // Asha
let message = "not available";
if (true) {
const localMessage = "available inside the block";
message = localMessage;
}
console.log(message);
// Declare before reading. Reading `status` above this line
// would throw: Cannot access 'status' before initialization.
const status = "ready";
console.log(status);
ReferenceError occurs when you try to use a variable that hasn't been declared or is not accessible in the current scope. Common causes include typos, using variables before declaration, or accessing block-scoped variables outside their block.
Declare the variable before using it with let, const, or var. Check for typos in variable names. Ensure the variable is in the correct scope. Use typeof to check if a variable exists before accessing it.
Temporal Dead Zone (TDZ) is the period between entering a scope and the actual declaration of a let/const variable. Accessing the variable during TDZ causes ReferenceError.
Explore 500+ free tutorials across 20+ languages and frameworks.