Lesson 41 of 80
TypeScript's namespace predates ES modules and solved the same problem — avoiding global naming collisions. ES modules won that fight everywhere except a few specific corners namespaces still quietly occupy.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Before import/export was a real, standardized JavaScript feature, TypeScript needed some way to organize code without dumping everything into the global scope. Its answer was namespace (originally called module, confusingly, before ES modules existed and TypeScript renamed the keyword to avoid the clash):
namespace Validation {
export interface Rule {
isValid(value: string): boolean;
}
export class LettersOnlyRule implements Rule {
isValid(value: string): boolean {
return /^[A-Za-z]+$/.test(value);
}
}
}
const rule = new Validation.LettersOnlyRule();
A namespace compiles to a plain JavaScript object at runtime — everything exported inside it becomes a property on that object, which is how Validation.LettersOnlyRule resolves. It's a real runtime construct, not just a type-space organizing tool.
// validation.ts
export interface Rule {
isValid(value: string): boolean;
}
export class LettersOnlyRule implements Rule {
isValid(value: string): boolean {
return /^[A-Za-z]+$/.test(value);
}
}
// main.ts
import { LettersOnlyRule } from "./validation";
const rule = new LettersOnlyRule();
Same idea — group related code, expose only what you export — but files, not namespace blocks, are the unit of organization, and a real bundler or the JavaScript runtime itself resolves the imports. This is a genuine, standardized JavaScript feature now (not a TypeScript-only construct that gets compiled away into something else), it tree-shakes properly, and every modern build tool is built around it. For any code you're writing today, import/export is the answer — namespace isn't "the old syntax for the same thing you should still consider," it's legacy.
declare global {
namespace Express {
interface Request {
user?: { id: string };
}
}
}
This is the one place namespace remains genuinely idiomatic today, not legacy — declaration merging for augmenting a third-party library's global types. TypeScript itself, and a lot of @types/* packages, still use namespace here because it's specifically designed to merge across declarations the way interface does, which lets you attach types to something like the global Express namespace without owning its source.
namespace isn't purely a type-space construct — it emits real runtime JavaScript, an actual object with properties, unlike most of what this track covers (types, interfaces, infer) which vanish entirely at compile time. This is a meaningful practical difference: a namespace has a runtime cost and a runtime identity that a type alias never does.module instead of namespace in older TypeScript code, it's the exact same feature under its original, now-deprecated name — namespace was introduced specifically to stop colliding with the new meaning of "module" once ES modules became the real JavaScript standard.namespace participates in declaration merging, the same mechanism interface uses. It's not really "using namespaces for their original organizational purpose"; it's using one specific side-effect of how namespaces merge."When would you use a namespace instead of an ES module?" is a question that tests whether you know the honest answer is "almost never, except when augmenting a global or a third-party library's types" — not whether you can recite namespace syntax. Recognizing namespace in a legacy or @types/* file and knowing whether it's a red flag (leftover pre-module code) or legitimate (global augmentation) is the actual skill being tested.