TypeScriptNamespaces

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

TS
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.

TS
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:

TS
// TypeScript source
namespace Greet {
  export function hello(name: string) {
    return `Hello, ${name}!`;
  }
}
      

JS
// Compiled JavaScript output
var Greet;
(function (Greet) {
  function hello(name) {
    return "Hello, " + name + "!";
  }
  Greet.hello = hello;
})(Greet || (Greet = {}));
      
Note
The IIFE pattern means multiple namespace blocks with the same name merge into a single object — this is how namespace augmentation works.
Nested Namespaces

TS
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):

TS
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.

TS
// shapes/circle.ts
/// <reference path="shapes.ts" />

namespace Shapes {
  export class Circle {
    constructor(public radius: number) {}
    area() { return Math.PI * this.radius ** 2; }
  }
}
      

TS
// 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; }
  }
}
      

TS
// shapes/shapes.ts — the root declaration
namespace Shapes {
  export interface Shape {
    area(): number;
  }
}
      

TS
// 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());
      
Warning
Triple-slash references are a legacy pattern. In any project using a bundler or 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:

TS
// 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.

TS
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

TS
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.

TS
// 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

Tip
If your project uses a bundler or Node.js, use ES modules. Reserve namespaces for declaration files, merging with existing constructs, or browser script environments where a module system is unavailable.
Namespaces vs Modules — Key Differences

TS
// ===== 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.