Several classic (non-module) JavaScript files loaded with plain script tags need to share one global namespace object. Explain the `var App = App || {};` plus IIFE-augmentation idiom, why it works regardless of file order, and why swapping `var` for `let` breaks it.
answer
- one global instead of dozens
- guarded declaration then augment
- var redeclaration is legal, hoisted to undefined
- order-independent by construction
- let self-reference hits the dead zone
basics
~20 sEach file writes var App = App || {}; then an IIFE that hangs its own members on App. Because var may be redeclared and is hoisted-initialized to undefined, whichever file loads first creates the object and the rest reuse it. let throws instead of reusing.
solid answer
~50 sEvery participating file opens with `var App = App || {};`, then runs an IIFE that receives the namespace and attaches its piece to it. That single line is both a create and a reuse: `var` redeclaration is legal and the binding is initialized to `undefined` before the line runs, so `App || {}` yields the existing object if some earlier file already made one and a fresh object otherwise. Nothing depends on load order. The IIFE then keeps that file's helpers private and adds only the intended members, so the global surface is one name instead of dozens. Swapping in `let` breaks both halves: within a file, `let App = App || {}` throws a ReferenceError because the initializer reads `App` while it is still in the temporal dead zone, and a second script declaring a top-level `let App` is a redeclaration error, since top-level lexical declarations in classic scripts share one global lexical environment.
code
javascript · 16 lines// file 1 — loaded in whichever order
var App = App || {};
(function (ns) {
const SEP = ', '; // stays private to this file
ns.text = { join(parts) { return parts.join(SEP); } };
})(App);
// file 2 — identical opening line, reuses the object file 1 created
var App = App || {};
(function (ns) {
ns.math = { clamp(n, lo, hi) { return Math.min(hi, Math.max(lo, n)); } };
})(App);
console.log(Object.keys(App)); // ['text', 'math']
console.log(App.text.join(['a', 'b'])); // 'a, b'
console.log(typeof SEP); // 'undefined' — never globalgo deeper
Recognise the two-part shape — a guarded var App = App || {} followed by an IIFE that attaches members — and know it exists to keep the global object down to one name.
Explain the mechanics: var redeclaration is legal and hoisted to undefined, which is what makes the guard order-independent, and the IIFE keeps each file's helpers off the global object.
Judge where the idiom still belongs — embedded widgets, injected snippets, build-free pages — and name what it cannot give you, such as build-time detection of a missing or mistyped member.
Own the boundary decision: which parts of the estate may ship as classic scripts at all, what the root namespace is called so third-party scripts cannot collide with it, and where initialization order is allowed to matter.
## The problem this solves In a classic script — a `<script>` without `type="module"` — top-level code runs in the global scope. A top-level `var` or function declaration becomes a property of the global object, and a plain assignment to an undeclared name does too. Load ten such files and every helper, constant and counter they define is sitting in one shared bag where any of them can silently clobber another. Debugging a collision like that is miserable, because the symptom appears in a file that is entirely correct. The namespacing idiom reduces that to one deliberate global. ## The idiom ```js // file: app.format.js var App = App || {}; (function (ns) { const PAD = 2; // private to this file function pad(n) { return String(n).padStart(PAD, '0'); } ns.format = { clock(h, m) { return `${pad(h)}:${pad(m)}`; }, }; })(App); ``` Two mechanisms are doing the work. The first line is the *guarded declaration*. `var` declarations are hoisted and initialized to `undefined` at the top of the scope, and redeclaring an existing `var` in the same scope is legal and does not reset it. So if `app.core.js` already ran `var App = App || {}`, this file's declaration is a no-op and `App` still holds the existing object; the `|| {}` branch only fires the first time. Every file can therefore carry the identical line and none of them cares who ran first. The second is the IIFE. It gives the file a private scope for its own helpers — `PAD` and `pad` here never reach the global object — and takes the namespace as a parameter, which makes the dependency explicit and gives it a short local name. A compact variant folds the guard into the call so the file has exactly one statement: ```js (function (ns) { ns.format = { /* ... */ }; })(window.App = window.App || {}); ``` This works because assignment is an expression that evaluates to the assigned value, so the argument is the namespace object either way. ## Why `let` and `const` do not fit Both halves of the idiom fail with a lexical declaration. Within a single file, `let App = App || {}` throws: ```js let App = App || {}; // ReferenceError: Cannot access 'App' before initialization ``` A `let` binding exists from the top of its scope but cannot be read until its declaration is evaluated — the temporal dead zone — and the initializer expression `App || {}` is evaluated *before* the binding is initialized. So the self-reference that makes the `var` form work is exactly what a lexical declaration forbids. Across files, top-level `let` and `const` in classic scripts go into one shared global lexical environment, so a second file declaring `let App` collides with the first and is a redeclaration error. Together those two facts mean the idiom is genuinely tied to `var` semantics; it is one of the few places where `var` behaviour is load-bearing rather than a legacy accident. If you want a lexical declaration, the workaround is to stop self-referencing: declare the namespace exactly once in a file that is guaranteed to load first, and have the other files only augment it. ## Namespacing versus modules Be honest about the era. Once files are loaded as ES modules, each file already has its own scope, imports state their dependencies explicitly, and the tooling can tell you at build time when a name is missing. Namespacing gives you none of that: the dependency between files is implicit in load order, a typo in `ns.fromat` is a silent undefined rather than an error, and everything remains mutable by anyone holding `App`. What keeps the idiom relevant is the code that genuinely cannot be a module — a snippet injected into a page you do not control, a widget distributed as a single script tag, a bookmarklet, an analytics or embed script, admin-panel scripts served without a build step. In those places you still want the one-global discipline, and the pattern is exactly right. ## Practical rules if you have to use it Pick one short, distinctive root name; a generic one like `App` or `Utils` is itself a collision risk on a page you share with third-party scripts. Never depend on file order for behaviour — only for the trivial creation of the root object. Keep initialization that must happen in a specific sequence in an explicit `App.init()` that a single entry file calls, rather than scattering side effects across IIFE bodies where the order is whatever the loader happened to produce.
- What would you write instead if every file must use `const`?Drop the self-reference. Declare the namespace exactly once, in an entry file that is guaranteed to load first, and have every other file only augment the existing object rather than re-declaring it. You trade order-independence for lexical declarations, so the load order becomes a real constraint you have to enforce.
- Why pass the namespace in as a parameter instead of just referring to `App` inside the IIFE?It makes the dependency explicit at the call site, gives the file a short local alias that is cheap to rename, and stops the body from silently picking up some other global if the outer name changes. It also documents in one glance what the file touches from outside its own scope.
- What goes wrong if two files both do `ns.util = { ... }` rather than adding distinct members?The second one wins and silently replaces the first, with no error anywhere — exactly the collision the namespace was supposed to prevent, just moved one level down. Either give each file its own sub-name, or merge deliberately with something like `ns.util = Object.assign(ns.util || {}, { ... })`.
saying these in an interview costs you the question
- Says `let` would work the same as `var` here
- Thinks the files must be loaded in a fixed order
- Claims the IIFE is unnecessary because var is scoped to the file
- Believes a namespace object gives the same guarantees as modules
- Says two files assigning the same member merge automatically