100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Namespaces in TypeScript

TypeScript's legacy 'internal modules' feature for grouping related code under a single global name, and why ES modules are now preferred.

Modules & ToolingAdvanced10 min readJul 8, 2026
Analogies

1. Introduction

Namespaces (formerly called "internal modules") are a TypeScript-specific construct for grouping related code — variables, functions, classes, interfaces — under a single named container, using the namespace keyword. They predate ES modules in the TypeScript language and were originally the primary way to organize code before import/export syntax became standard in JavaScript.

🏏

Cricket analogy: A namespace like namespace Scoring { ... } is like grouping every scoring function (runs, extras, wickets) under one team dressing-room banner before the ICC's modern import/export transfer rules existed, so everything lived under one shared name.

Namespaces compile to plain JavaScript objects that attach properties to a shared global (or module-scoped) variable, which makes them useful in contexts without a module loader — for example, multiple <script> tags sharing global state in a browser, or organizing large ambient type declaration files. In modern application code, ES modules are strongly preferred, and namespaces are considered legacy, but they still appear in older codebases and in certain .d.ts declaration file patterns.

🏏

Cricket analogy: A namespace compiling to a plain object attaching properties to one shared global variable is like several broadcast vans on the same ground sharing one shared scoreboard feed without a formal relay system — useful for old stadium setups, but modern grounds use dedicated per-camera feeds (ES modules) instead.

2. Syntax

typescript
// shapes.ts
namespace Shapes {
  export interface Shape {
    area(): number;
  }

  export class Circle implements Shape {
    constructor(private radius: number) {}
    area(): number {
      return Math.PI * this.radius ** 2;
    }
  }

  // Not exported — private to the namespace
  function describe(shape: Shape): string {
    return `Area: ${shape.area().toFixed(2)}`;
  }

  export function report(shape: Shape): string {
    return describe(shape);
  }
}

// usage.ts (same compiled output / with <reference> in older setups)
const c = new Shapes.Circle(2);
console.log(Shapes.report(c));

// Nested namespace
namespace Company.Department {
  export const name = "Engineering";
}
console.log(Company.Department.name);

3. Explanation

ES modules (import/export) are the modern standard, while namespace (formerly "internal modules") is largely discouraged for new code but still seen in older codebases and global-script contexts. A namespace block compiles to an immediately-invoked function expression (IIFE) that builds up a plain object; only members marked export are attached to that object and visible from outside, exactly mirroring how ES module exports work, but scoped to a single global identifier instead of a file-based module graph.

🏏

Cricket analogy: A namespace block compiling to an IIFE is like an old-school scoring booth that builds one shared results object behind closed doors, only posting the marked export stats onto the public board, exactly mirroring how a modern module publishes its exports — except everything hangs off one shared board instead of separate per-match sheets.

Namespaces can be split across multiple files using triple-slash reference directives (/// <reference path="..." />) so the compiler knows how to stitch them together and preserve declaration order, and they can be nested (namespace A.B { ... }) to mimic package-like hierarchies. Because everything ultimately hangs off one runtime object, namespaces don't get the same tree-shaking, lazy-loading, or per-file dependency resolution benefits that bundlers give to ES modules.

🏏

Cricket analogy: Splitting namespace Cricket.Scoring across files with /// <reference path="..." /> is like stitching together multiple scorers' handwritten sheets in a fixed order into one master scorebook; because it's all one shared book, a bundler can't tree-shake out the unused fielding-drill notes the way it could drop an unused ES module file.

Namespaces remain genuinely useful in one place: ambient declaration files (.d.ts) for libraries that expose a single global variable (e.g., a script-tag library like an old jQuery plugin), where there is no module system to import from and the library's shape must be described as a namespace on the global scope.

Gotcha: mixing namespaces and ES modules in the same file is legal TypeScript but produces confusing, hard-to-maintain code, and combining namespace with export = / import ... = require(...) (needed to actually export a namespace from a module) is a common source of interop errors with bundlers that expect standard ESM default/named exports. For new projects, prefer ES modules entirely and avoid namespaces unless you're maintaining legacy code or writing global-script declaration files.

4. Example

typescript
// validation.ts — a namespace split conceptually into two logical groups
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }

  export class LettersOnlyValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^[A-Za-z]+$/.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^\d{5}(-\d{4})?$/.test(s);
    }
  }
}

const validators: { [name: string]: Validation.StringValidator } = {
  letters: new Validation.LettersOnlyValidator(),
  zip: new Validation.ZipCodeValidator(),
};

for (const name of Object.keys(validators)) {
  console.log(`${name}: ${validators[name].isValid("12345")}`);
}

5. Output

text
letters: false
zip: true

Compiled JavaScript wraps the namespace body in an IIFE roughly equivalent to:
var Validation;
(function (Validation) {
  ... class definitions attached as Validation.LettersOnlyValidator, Validation.ZipCodeValidator ...
})(Validation || (Validation = {}));

Only the `export`-ed classes/interfaces are accessible as Validation.X from outside the IIFE.

6. Key Takeaways

  • Namespaces (formerly "internal modules") group related code under one named object using the namespace keyword.
  • Only members marked export inside a namespace are visible outside it; the rest stay private to the compiled IIFE.
  • Namespaces compile to a runtime object built by an IIFE, not to a file-based module.
  • ES modules are the modern standard for application code; namespaces are legacy, mainly useful for global-script .d.ts declarations.
  • Namespaces can be nested (namespace A.B) and split across files with triple-slash references, but lose bundler-level tree-shaking benefits.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#TypeScriptProgrammingStudyNotes#Programming#NamespacesInTypeScript#Namespaces#Syntax#Explanation#Example#StudyNotes#SkillVeris