ReferenceError: Cannot access 'X' before initialization

Quick answer

You read or wrote a let/const/class binding before its declaration line actually ran — the temporal dead zone (TDZ). Unlike var, which hoists as undefined, let/const hoist the name but leave it uninitialized and throw on any access. Fix it by moving the declaration above the use, not by switching back to var.

The exact error string

console.log(count);
let count = 10;
// Uncaught ReferenceError: Cannot access 'count' before initialization

{
  console.log(x);   // ❌ same TDZ, even though x exists later in this block
  let x = 5;
}

Why let/const behave differently from var

var declarations are hoisted and initialized to undefined immediately, so an early read just silently gives undefined — a quieter, harder-to-spot bug. let/const/class are hoisted but stay uninitialized from the top of the scope until their declaration line runs. That span is the temporal dead zone: any access inside it throws, on purpose, so the mistake surfaces immediately instead of silently producing undefined.

console.log(a);   // undefined — var hoists AND initializes
var a = 1;

console.log(b);   // ❌ Cannot access 'b' before initialization — TDZ
let b = 1;

Cause 1: using a variable before its declaration line

function greet() {
  console.log(message);   // ❌ still in message's TDZ
  let message = "hi";
}

function greet() {
  let message = "hi";     // ✅ declare first
  console.log(message);
}

Cause 2: a self-referencing default parameter

function f(a = a) { }
f();
// ❌ Cannot access 'a' before initialization — a's default reads a itself

function f(a, b = a) { }   // ✅ default refers to a different, already-set param
f(5);

Cause 3: class fields that depend on each other

class Config {
  timeout = retries * 1000;   // ❌ retries hasn't initialized yet (declared below)
  retries = 3;
}

class Config {
  retries = 3;                 // ✅ declare first
  timeout = this.retries * 1000;
}

Cause 4: block scope shadowing an outer variable

let value = "outer";
{
  console.log(value);   // ❌ NOT "outer" — this block's own `value` shadows it,
  let value = "inner";  //    and that inner `value` is in its TDZ right here
}

let value = "outer";
{
  let value = "inner";   // ✅ declare before any reference inside the block
  console.log(value);
}

Common variants at a glance

PatternWhy it's TDZFix
use before declare, same scopedeclaration line hasn't executed yetmove the declaration up
function f(a = a)default value reads its own uninitialized paramreference a different param, or restructure
class field orderinga field depends on one declared below itreorder fields top-to-bottom by dependency
shadowed block variableinner let shadows outer name for the whole blockdeclare the inner variable first, or rename it
switching to var "fixes" itremoves the check, not the bugreorder code instead

Debugging checklist

Frequently Asked Questions

What is the temporal dead zone (TDZ)?

The span of code between the start of a scope and the line where a let/const/class binding is actually declared. The name is hoisted (JavaScript knows it exists) but is uninitialized during that span — any read or write attempt throws "Cannot access before initialization" instead of silently returning undefined.

Why doesn't this happen with var?

var declarations are hoisted AND initialized to undefined immediately, so reading one before its assignment line just gives undefined with no error — a different, quieter bug. let/const/class are hoisted but left uninitialized until their declaration executes, which is exactly what makes the TDZ detectable and why this error exists.

Why does a self-referencing default parameter throw this?

function f(a = a) { } tries to read a while evaluating a's own default value, before a has been assigned — a is in its own TDZ at that point. Reference a different, already-initialized variable for the default, or restructure so the parameter doesn't depend on itself.

Why does this happen inside a class field or constructor?

Class declarations are also subject to the TDZ — referencing a class (or one of its static/instance fields that depends on another not-yet-initialized field) before the class body finishes evaluating throws the same error. Order class fields so each only depends on fields declared above it.

Why does swapping let for var 'fix' this?

It removes the error by removing the safety net, not by fixing the underlying issue — the variable is still logically used before it has a meaningful value, now silently as undefined. Prefer reordering your code so the declaration runs before any use, rather than switching to var to suppress the error.

How do I fix 'Cannot access before initialization' quickly?

Move the variable's declaration above its first use, or move the use below the declaration. If a block-scoped loop or closure is involved, confirm you're not capturing a reference to a binding that hasn't run yet in that particular iteration or call.

More JavaScript & runtime errors

Browse the full reference for JavaScript, Node.js, and database errors — exact message, cause, and fix.

All Error References Assignment to constant variable HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.