Namespaces
Namespaces are TypeScript's original way of grouping related code under one name, from before JavaScript had modules. You'll rarely write new ones today, but you'll meet them in older codebases and type definition files, so it's worth knowing how they work.
Declaring a namespace
namespace Validation { const emailPattern = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; export function isEmail(value: string): boolean { return emailPattern.test(value); } export function isPostcode(value: string): boolean { return /^\d{5}$/.test(value); } } console.log(Validation.isEmail("a@b.co")); console.log(Validation.isPostcode("1234"));
true false
Only members marked export are visible outside the namespace — emailPattern is private to it. You access exported members with dot notation, like properties on an object.
What a namespace compiles to
Unlike most TypeScript constructs, a namespace produces real JavaScript — an object built inside an immediately invoked function:
var Validation; (function (Validation) { const emailPattern = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; function isEmail(value) { return emailPattern.test(value); } Validation.isEmail = isEmail; // ... })(Validation || (Validation = {}));
This "IIFE" pattern was the standard way to avoid polluting the global scope before ES modules existed.
Nested namespaces and aliases
namespace App { export namespace Models { export interface Task { title: string; done: boolean; } } export namespace Utils { export const summary = (tasks: Models.Task[]) => `${tasks.filter(t => t.done).length}/${tasks.length} done`; } } import Task = App.Models.Task; const tasks: Task[] = [ { title: "plan", done: true }, { title: "build", done: false }, ]; console.log(App.Utils.summary(tasks));
1/2 done
import Task = App.Models.Task is a namespace alias — a shortcut name, not a module import. It's unique to namespaces.
Namespaces vs modules
| Namespaces | ES modules | |
|---|---|---|
| Scope | A named object (often global) | One file |
| Dependencies | Implicit, by load order | Explicit import statements |
| Tree-shaking by bundlers | Poor | Good |
| Standard JavaScript? | No — TypeScript-only | Yes |
For new code, use modules. Each file already acts as its own namespace, so wrapping a module's contents in a namespace just adds an extra layer to type.
Where namespaces still appear
Declaration files for global libraries
Libraries that attach themselves to a global variable (older jQuery plugins, analytics snippets, Google Maps' google.maps) are often described with a namespace in a .d.ts file:
declare namespace Analytics { interface Event { name: string; props?: Record<string, string | number>; } function track(event: Event): void; }
The declare keyword means "this exists at runtime somewhere else — I'm only describing it". Lesson 33 covers declaration files in detail.
Grouping types next to a function or class
A namespace with the same name as a function or class merges with it (lesson 34), which some libraries use to attach related types:
function format(value: number, options: format.Options = {}) { return value.toFixed(options.decimals ?? 2); } namespace format { export interface Options { decimals?: number; } } const opts: format.Options = { decimals: 1 }; console.log(format(3.14159, opts));
3.1
erasableSyntaxOnly compiler option (lesson 44). That's one more reason modern code avoids them.