In JavaScript, `new Widget()` written above `function Widget() {}` works, but the same call above `class Widget {}` throws. Explain what a class declaration does differently.
answer
- both are hoisted, one is not initialized
- the binding exists but has no value yet
- temporal dead zone, like let
- 'Cannot access before initialization'
basics
~20 sA class declaration is hoisted but stays uninitialized until evaluated, so touching it earlier throws ReferenceError: Cannot access before initialization. A function declaration is hoisted and initialized with the function, so it is callable from anywhere in its scope.
solid answer
~50 sBoth declarations are hoisted in the sense that the binding exists from the moment the scope is entered, but only the function declaration is *initialized* early. A class declaration behaves like `let`: the name is reserved from the top of the block yet sits in the temporal dead zone until the declaration is evaluated, so an earlier `new Widget()` throws `ReferenceError: Cannot access 'Widget' before initialization` — not "Widget is not defined", which is the tell that the binding exists. Class declarations are also block-scoped, and at the top level of a classic script they do not create a `globalThis.Widget` property the way a function declaration does. The practical rule is that a class must be *evaluated* before it is used, so define it above its first use — though a function that merely mentions the class is fine, because that reference resolves when the function runs, not when it is defined.
code
javascript · 11 linestry { new Widget(); } catch (e) { console.log(e.name, '|', e.message); }
// ReferenceError | Cannot access 'Widget' before initialization
try { console.log(typeof Widget); } catch (e) { console.log('typeof throws too'); }
console.log(typeof neverDeclared); // 'undefined' - no binding at all, no throw
new Gadget(); // fine
function Gadget() { }
class Widget {}
console.log(new Widget() instanceof Widget); // true, after the declarationgo deeper
Remember the practical rule: declare a class before the code that uses it, because unlike a function declaration it cannot be used above its own definition.
Explain the mechanism — the binding is created at scope entry but stays uninitialized in the temporal dead zone, so reads throw a ReferenceError until the declaration is evaluated.
Read the two ReferenceError messages apart under time pressure and know that class declarations are block-scoped and add no property to the global object, which breaks registries that look names up as strings.
Take a position on module and file organization so evaluation order is never load-bearing, and treat definition-time work inside class bodies as something to keep minimal and explicit.
## Two different meanings of "hoisted" Every declaration in a scope is registered when that scope is entered — that is why `Widget` is a known name throughout the block in both cases. What differs is *when the binding gets a value*. - A function declaration is created **and initialized** with the fully built function at scope entry. Calling it above its own text works. - A class declaration creates the binding at scope entry but leaves it **uninitialized** until control reaches the declaration. The window in between is the temporal dead zone, and any read of the name during it throws. ```js new Widget(); // ReferenceError: Cannot access 'Widget' before initialization class Widget {} new Gadget(); // works function Gadget() {} ``` ## The error message is the diagnostic The two failures look similar in a stack trace but mean different things: - `ReferenceError: Cannot access 'Widget' before initialization` — the binding exists in this scope; you are too early. - `ReferenceError: Widget is not defined` — no such binding anywhere in the scope chain; the name is wrong, or the import/definition is missing entirely. Because the binding exists from the top of the block, it also *shadows* any outer binding of the same name for the whole block: ```js class Widget { static tag = 'outer'; } { // console.log(Widget); // ReferenceError - the inner Widget already shadows the outer one class Widget { static tag = 'inner'; } } ``` The outer `Widget` is unreachable earlier in that block, which surprises people who expect the outer one to be visible until the inner declaration appears. ## Block scoping and no global property A class declaration is block-scoped, exactly like `let` and `const`: ```js if (cond) { class Tmp {} } // Tmp is not visible here ``` A function declaration at the top level of a classic (non-module) script creates a property on the global object, so `globalThis.Gadget` is the function. A class declaration does not: it lives in the global *lexical* environment, so `globalThis.Widget` is `undefined` even though `Widget` resolves fine as an identifier. Code that reaches for globals by string name — a plugin registry doing `globalThis[name]`, say — silently fails to find classes. ## What happens when the declaration is evaluated Evaluating the declaration is real work, not just a binding assignment: the constructor function is created, the prototype object is populated with the methods, static members are installed, static initialization blocks run, and any heritage expression and computed method key are evaluated at that moment. So the ordering rule is about *evaluation order in the file*, not about parsing. ```js const KEY = 'run'; class Job { [KEY]() {} } // KEY must already be initialized here ``` ## The pattern that still works A reference inside a function body is resolved when that function is called, so this is fine: ```js export function makeWidget() { return new Widget(); } // fine: runs later class Widget {} ``` That is why "define classes before use" is about the *executed* order, not the textual order of every mention. Mutual references between two classes work for the same reason as long as neither is instantiated during the definitions themselves. ## Why the design is this way Early function hoisting is a legacy of the original language and is genuinely convenient, but it also lets you call a function before the file has set up anything it depends on. The temporal dead zone was introduced with `let`/`const` and applied to classes so that a binding cannot be observed in a half-built state — a class body can run arbitrary code at definition time, and reading the class before that code has run would expose an object that is not what the author wrote. Turning it into a loud, immediate `ReferenceError` is strictly better than handing back a partially initialized class. The practical consequences to internalize: order matters for classes, the specific error text tells you whether the name is unknown or merely early, and `typeof` does not save you — `typeof Widget` before the declaration throws too, unlike `typeof someCompletelyUndeclaredName`, which quietly returns `"undefined"`.
- How do you tell a temporal-dead-zone error from a misspelled identifier at a glance?Read the message. "Cannot access 'X' before initialization" means the binding exists in scope and you touched it too early — move the use below the declaration. "X is not defined" means no binding exists anywhere on the scope chain, so the name is wrong or the definition is missing.
- Two classes reference each other. Does the declaration order matter?Only if one of them uses the other while the declarations are being evaluated. Mentioning a class inside a method body is resolved at call time, so mutual references are fine. Instantiating the second class in the first one's static initialization, however, runs during evaluation and hits the dead zone.
saying these in an interview costs you the question
- Says class declarations are not hoisted at all
- Expects the error to be 'Widget is not defined'
- Thinks typeof safely probes a class before its declaration
- Assumes a top-level class becomes a property of globalThis
- Believes any textual mention before the class is an error