Namespaces
Namespaces are TypeScript's original mechanism for organizing code within a single file or across multiple files. They compile to JavaScript immediately-invoked function expressions (IIFEs) that hang declarations off a shared object, preventing global pollution.
In modern TypeScript projects, ES modules have largely replaced namespaces for
code organization. But namespaces still have legitimate uses: authoring declaration
files (.d.ts), augmenting third-party type declarations, and working in
environments without a module bundler (e.g. browser <script> tags).
Declaring a Namespace
namespace Geometry {
export interface Point {
x: number;
y: number;
}
export function distance(a: Point, b: Point): number {
return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);
}
export const ORIGIN: Point = { x: 0, y: 0 };
}
// Usage — access through the namespace name
const p1: Geometry.Point = { x: 3, y: 4 };
const d = Geometry.distance(p1, Geometry.ORIGIN); // 5
Only members marked with export are accessible outside the namespace. Non-exported
members are private to the namespace block.
namespace Auth {
// Private helper — not exported, invisible outside Auth
function hashPassword(plain: string): string {
return btoa(plain); // simplified — do NOT use in production
}
export function login(user: string, pass: string): boolean {
const hash = hashPassword(pass); // accessible here
return hash.length > 0;
}
}
Auth.login('alice', 'secret'); // ✓
// Auth.hashPassword('x'); // ✗ Error: not exported
What Namespaces Compile To
TypeScript compiles a namespace into an IIFE that attaches exported members to a plain JavaScript object:
// TypeScript source
namespace Greet {
export function hello(name: string) {
return `Hello, ${name}!`;
}
}
// Compiled JavaScript output
var Greet;
(function (Greet) {
function hello(name) {
return "Hello, " + name + "!";
}
Greet.hello = hello;
})(Greet || (Greet = {}));
Nested Namespaces
namespace App {
export namespace UI {
export interface Theme {
primary: string;
secondary: string;
}
export function applyTheme(theme: Theme): void {
document.body.style.setProperty('--primary', theme.primary);
}
}
export namespace API {
export interface Config {
baseUrl: string;
timeout: number;
}
export async function get<T>(path: string, config: Config): Promise<T> {
const response = await fetch(`${config.baseUrl}${path}`);
return response.json();
}
}
}
// Access nested namespaces
const theme: App.UI.Theme = { primary: '#6200ea', secondary: '#03dac6' };
App.UI.applyTheme(theme);
const config: App.API.Config = { baseUrl: 'https://api.example.com', timeout: 5000 };
Namespace Aliases
Deep namespace paths can be aliased with import = (a TypeScript-only syntax):
namespace Company.Product.Module.Utils {
export function doSomething(): void {
console.log('doing something');
}
}
// Without alias — verbose
Company.Product.Module.Utils.doSomething();
// With alias — clean
import Utils = Company.Product.Module.Utils;
Utils.doSomething();
// Aliases can also import CommonJS modules in namespace style
import fs = require('fs');
const data = fs.readFileSync('./file.txt', 'utf-8');
Multi-File Namespaces (Triple-Slash References)
Before ES modules, large codebases split namespaces across multiple files using
triple-slash reference directives (/// <reference path="..." />). TypeScript
merges them during compilation.
// shapes/circle.ts
/// <reference path="shapes.ts" />
namespace Shapes {
export class Circle {
constructor(public radius: number) {}
area() { return Math.PI * this.radius ** 2; }
}
}
// shapes/rectangle.ts
/// <reference path="shapes.ts" />
namespace Shapes {
export class Rectangle {
constructor(public width: number, public height: number) {}
area() { return this.width * this.height; }
}
}
// shapes/shapes.ts — the root declaration
namespace Shapes {
export interface Shape {
area(): number;
}
}
// main.ts — reference the root and use the merged namespace
/// <reference path="shapes/shapes.ts" />
/// <reference path="shapes/circle.ts" />
/// <reference path="shapes/rectangle.ts" />
const c = new Shapes.Circle(5);
const r = new Shapes.Rectangle(4, 6);
console.log(c.area(), r.area());
tsc with include/filesarrays, you should not need them. Use ES module imports instead.Namespace Merging
Multiple namespace declarations with the same name in the same scope merge into a single namespace. This is useful for extending namespaces from other files or declaration files:
// First declaration
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
// Second declaration — merged with the first
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// Merged result — both exports are accessible
const validator = new Validation.LettersOnlyValidator();
validator.isAcceptable('hello'); // true
Merging Namespaces with Classes
TypeScript allows you to merge a namespace with a class. This enables static-like members on the class that are actually namespace exports — a common pattern in declaration files for libraries that attach properties to constructor functions.
class Animal {
constructor(public name: string) {}
}
namespace Animal {
export interface Metadata {
species: string;
endangered: boolean;
}
export function describe(a: Animal): string {
return `${a.name} is an animal`;
}
}
const lion = new Animal('Lion');
const meta: Animal.Metadata = { species: 'Panthera leo', endangered: false };
console.log(Animal.describe(lion));
Merging Namespaces with Enums
enum Direction {
Up,
Down,
Left,
Right,
}
// Merge a namespace onto the enum to add utility functions
namespace Direction {
export function opposite(d: Direction): Direction {
switch (d) {
case Direction.Up: return Direction.Down;
case Direction.Down: return Direction.Up;
case Direction.Left: return Direction.Right;
case Direction.Right: return Direction.Left;
}
}
export function isVertical(d: Direction): boolean {
return d === Direction.Up || d === Direction.Down;
}
}
Direction.opposite(Direction.Up); // Direction.Down
Direction.isVertical(Direction.Left); // false
Namespaces in Declaration Files
The primary modern use of namespaces is inside .d.ts declaration files for
libraries that ship as global scripts (UMD, browser globals). Most DefinitelyTyped
(@types/*) packages use namespaces extensively.
// global-lib.d.ts — typing a library loaded via <script>
declare namespace MyLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
interface GreetSettings {
greeting: string;
duration?: number;
color?: string;
}
class Greeter {
constructor(settings: GreetSettings);
greet(): void;
}
}
// In consuming code (no import needed — MyLib is a global)
const greeter = new MyLib.Greeter({ greeting: 'Hello' });
greeter.greet();
When to Use Namespaces vs Modules
Scenario | Use Namespaces | Use ES Modules |
|---|---|---|
Browser globals / UMD library typing | Yes | No |
Authoring .d.ts declaration files | Often | Sometimes |
Merging with classes or enums | Yes | No |
Application code organization | No | Yes |
Shared library code | No | Yes |
Module bundler (Vite, webpack) | No | Yes |
Node.js project | No | Yes |
Namespaces vs Modules — Key Differences
// ===== NAMESPACE approach =====
namespace MathUtils {
export function add(a: number, b: number) { return a + b; }
}
// Compiles to an IIFE + object property
// Included by referencing the file, not via import
// ===== MODULE approach =====
// math-utils.ts
export function add(a: number, b: number) { return a + b; }
// consumer.ts
import { add } from './math-utils';
// Uses the module system (bundler / Node.js)
// Tree-shakeable — unused exports are removed by the bundler
Common Mistakes
Mixing namespaces and ES modules in the same file causes confusion — pick one.
Forgetting "export" inside a namespace makes the member inaccessible from outside.
Using triple-slash references in a project that already uses tsconfig "include" — they are redundant.
Creating deep namespaces when a simple module structure would be clearer.
Using namespaces for new application code when ES modules are available.
Summary
Namespaces are TypeScript-specific and compile to IIFE-based JavaScript objects.
Use export inside a namespace to make members accessible from outside.
Multiple namespace blocks with the same name merge into one.
Namespaces can merge with classes and enums to add extra members.
The primary modern use is in .d.ts files for global/UMD libraries.
For application code, prefer ES modules — they are tree-shakeable and work with all modern tooling.