8.5 — Namespace vs Modules
Namespaces share a roof; Modules need trade agreements
TypeScript has TWO ways to organize code: Namespaces (the OLD way, pre-ES6) and Modules (the MODERN way, ES6+). They look different, work differently, and serve very different purposes. Understanding both is crucial because you will definitely encounter namespaces in legacy codebases and .d.ts declaration files, even if you only write modules in your new projects.
Think of the Nizam's Palace Complex versus the Independent Shops in Laad Bazaar. Namespaces are like the ROOMS inside the Nizam's palace — the Treasury Room, the Durbar Hall, the Kitchen. They are all UNDER ONE ROOF. You can walk from the Treasury to the Kitchen without going outside — they share the same building, which represents the global scope. You just use the room name to find what you need: Palace.Treasury.open(), Palace.Kitchen.cook(). Everything is connected internally.
Modules, on the other hand, are like the INDEPENDENT SHOPS in Laad Bazaar. Each shop is its own separate building with its own entrance and exit. To get items from one shop to another, you need an OFFICIAL TRADE AGREEMENT: import { bangles } from "./bangle-shop". No trade agreement? No access. Modules are completely self-contained; namespaces share a global space.
Seedha samjho: Namespace = palace rooms under one roof, Modules = independent shops needing trade agreements! In the modern TypeScript world, modules are the standard. But the old palace rooms (namespaces) still exist in many places, so you need to know how to navigate them without getting lost.
Namespaces group related code under a named container. They were TypeScript's ORIGINAL module system, created before ES6 modules existed, to solve the "global scope pollution" problem in the pre-module era. The syntax uses the namespace keyword, and the export keyword makes items accessible outside the namespace:
namespace Palace {
export function open() {
console.log("Doors are open!");
}
export const name = "Golconda";
// Not exported = private
function clean() { ... }
}
Palace.open(); // ✓ Accessible
Palace.name; // ✓ "Golconda"
Palace.clean();// ✗ Error! Not exported
Without export, items are private to the namespace. You can also nest namespaces to create a hierarchy, just like rooms inside wings of the palace:
namespace Palace.Kitchen {
export function cook() {
console.log("Cooking Biryani!");
}
}
Palace.Kitchen.cook();
Under the hood, namespaces are compiled to IIFEs (Immediately Invoked Function Expressions) in JavaScript. They create a closure that holds the "private" variables and returns an object with the "exported" ones. This was a clever workaround before JavaScript had a native module system.
In the old days, when namespaces were spread across multiple files, you had to use the triple-slash reference directive to tell the compiler about dependencies:
/// <reference path="palace.ts" />
/// <reference path="kitchen.ts" />
Palace.Kitchen.cook();
This /// <reference path="..." /> directive is old-school and should be avoided in modern code! It relies on the compiler to figure out the order and is completely replaced by the module import syntax. You'll mostly see this in legacy .d.ts files today.
Modules are the modern JavaScript standard for organizing code. ANY file with a top-level import or export statement is automatically treated as a module in TypeScript. No special syntax or keywords are needed to declare it — just use import or export and the file becomes a module!
// kitchen.ts
export function cook() {
console.log("Cooking Biryani!");
}
export default class Kitchen {
// ...
}
// app.ts
import Kitchen from "./kitchen";
import { cook } from "./kitchen";
cook(); // ✓
new Kitchen(); // ✓
Here are the key differences from namespaces that you must understand:
1. Scope: Modules have their OWN scope — nothing is global unless explicitly imported or exported. Namespaces live in the global scope by default.
2. Organization: Modules use file-based organization — typically one module per file. Namespaces can span multiple files or be crammed into one file.
3. Dependencies: Modules have EXPLICIT dependencies — you see every import at the top of the file. Namespaces use <reference> directives or rely on script load order in HTML, which is fragile.
4. Tooling: Modules work with bundlers (webpack, Vite, esbuild) out of the box. Namespaces need special configuration and are often poorly supported by modern tools.
5. Standard: Modules are the official JavaScript standard (ES6+). Namespaces are a TypeScript-only construct that never made it into the JavaScript language.
You can also re-export symbols from another module, which is incredibly useful for creating barrel files (consolidating exports):
// index.ts (barrel file)
export { cook } from "./kitchen";
export { serve } from "./dining";
This makes modules the clear winner for any modern TypeScript project. They are robust, explicit, and natively supported by the JavaScript ecosystem.
Mixing namespaces and modules is where developers suffer the most pain. Let's walk through the common traps at this Darbar so you don't make these mistakes in your codebase!
Trap 1: Mixing Namespaces and Modules. This is the BIGGEST trap! If a file has a top-level import or export, it becomes a MODULE. Any namespace inside that module is LOCAL to that module, not global. You cannot casually mix them and expect global behavior. Pick one approach: use modules (recommended) or namespaces (legacy only).
Trap 2: Global Augmentation from Modules. If you need to add something to the global scope from inside a module, you must use the declare global syntax:
// Inside a module file
declare global {
interface Window {
myApp: any;
}
}
window.myApp = {}; // ✓
However, modifying the global scope is usually a code smell. Avoid it unless you have a very specific requirement (like polyfills).
Trap 3: The export namespace Anti-Pattern. This is unfortunately common in tutorials, but it forces consumers to write extra dot notation for no reason:
// BAD: Unnecessary nesting
export namespace Utils {
export function helper() {}
}
// Consumer: Utils.helper()
// GOOD: Direct export
export function helper() {}
// Consumer: helper()
Just export the functions directly! ES modules already provide the namespacing through the file system and import syntax.
Trap 4: Forgetting export in Namespaces. If you forget to export a function or variable inside a namespace, it is completely private and inaccessible from the outside. This might be what you want, but it often leads to confusion for beginners wondering why Palace.secret() throws an error.
Trap 5: Script vs Module Confusion. A .ts file with NO imports or exports is a SCRIPT, meaning it shares the global scope. If you later add a single import to that file, it instantly becomes a MODULE. All its previously "global" declarations become file-scoped, which can break other files that relied on them! Always be mindful of this transition.
Here's your complete cheatsheet for Namespaces vs Modules — the Nizam's Palace vs Laad Bazaar handbook. Pin this to your desk, bhai!
Namespaces:
namespace Palace {
export function open() {}
}
Palace.open();
- Global scope (shared roof)
- TypeScript-only construct
- Uses
<reference path="..." /> - Pre-ES6 era solution
- Use ONLY for
.d.tsfiles & legacy code
Modules:
// kitchen.ts
export function cook() {}
// app.ts
import { cook } from "./kitchen";
- Own scope (independent shop)
- JavaScript ES6+ standard
- Uses
import/export - Works natively with bundlers
- Use for ALL modern code
Key Differences:
// Namespaces = Global Scope
// Modules = Local File Scope
// Namespaces = TS-only
// Modules = JS Standard
// Namespaces = <reference>
// Modules = import
// Modules work with bundlers
// Namespaces need config
Decision Framework:
- New project? → Always use Modules.
- Writing
.d.tstypes? → Namespaces are acceptable. - Working in old code? → Keep existing namespaces, don't mix.
- Need to organize? → Use folders and barrel files, not namespaces.
The golden rule: "Namespaces are the old palace rooms — everything under one roof. Modules are the modern Laad Bazaar shops — independent with trade agreements. For new code, always use modules, bhai. Don't live in the old palace!"
Key Points
- Namespaces are the old pre-ES6 way to group code under a global container
- Modules are the modern ES6+ standard using import/export with their own scope
- A file with any top-level import/export is a module; otherwise, it is a script
- Never mix namespaces and modules casually — namespaces inside modules are local
- Avoid the export namespace anti-pattern; export functions directly instead
- Always use modules for new projects; namespaces are only for .d.ts and legacy code
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login