Namespaces

Reviewed & published by Brayan K

A namespace is TypeScript's older way of grouping related code under one named object so it doesn't leak into the global scope, compiling down to a simple immediately-invoked function that exposes only its exported members.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A namespace is like a labelled drawer in a filing cabinet. Everything related to "Geometry" goes in the Geometry drawer, so its names never collide with the "Banking" drawer next to it. The drawer also has a private back compartment (un-exported helpers) that only the drawer's own contents can reach. ES modules are the modern upgrade: instead of one big cabinet, each file is its own drawer with its own lock.

1. Declaring a Namespace & the Compiled IIFE

You declare a namespace with namespace Name { ... } and mark the parts you want visible with export. The compiler turns this into an IIFE that creates one object and bolts the exported members onto it. Anything you don't export becomes a private function trapped inside the closure — which is exactly how the namespace keeps helpers hidden.

// The SAME thing in real TypeScript source:
namespace Geometry {
  export const PI = 3.14159;

  export function areaOfCircle(r: number): number {
    return PI * r * r;
  }

  // No 'export' -> private to the namespace, invisible outside:
  function square(n: number): number { return n * n; }
  export function areaOfSquare(side: number): number {
    return square(side);
  }
}

Geometry.PI;              // 3.14159
Geometry.areaOfCircle(2); // 12.566...
// Geometry.square(4);    // ❌ 'square' is not exported
// A TypeScript 'namespace' is a compile-time grouping. It COMPILES to a tiny
// IIFE (Immediately Invoked Function Expression) that builds ONE object and
// attaches only the EXPORTED members to it. Here is that compiled pattern by hand:

var Geometry;
(function (Geometry) {
  Geometry.PI = 3.14159;

  function areaOfCircle(r) {
    return Geometry.PI * r * r;
  }
  Geometry.areaOfCircle = areaOfCircle;   // exported -> attached to Geometry

  // A non-exported helper stays PRIVATE inside the IIFE:
  function square(n) { return n * n; }
  Geometry.areaOfSquare = (side) => square(side);
})(Geometry || (Geometry = {}));

console.log(Geometry.PI);                          // 3.14159
console.log(Geometry.areaOfCircle(2).toFixed(2));  // 12.57
console.log(Geometry.areaOfSquare(4));             // 16
console.log(typeof Geometry.square);               // undefined (never exported)

2. Nested Namespaces

Namespaces can nest, producing a dotted path like App.Models.User. At runtime that's just nested objects: App has a Models property which has the user helpers. When the path gets long, an import Alias = App.Models shorthand keeps your code readable.

// Nesting in TS source — read it as folders of related code:
namespace App {
  export namespace Models {
    export interface User { name: string; role: string }
    export function makeUser(name: string): User {
      return { name, role: "member" };
    }
  }
}

const u: App.Models.User = App.Models.makeUser("Ada");
// You can shorten a long path with an alias import:
import Models = App.Models;
const v = Models.makeUser("Grace");
// NESTED namespaces compile to nested objects. App.Models.User becomes an
// object 'App' with a property 'Models', which has the User helpers on it.

var App;
(function (App) {
  let Models;
  (function (Models) {
    function makeUser(name) {
      return { name, role: "member" };
    }
    Models.makeUser = makeUser;
  })(Models = App.Models || (App.Models = {}));
})(App || (App = {}));

const u = App.Models.makeUser("Ada");
console.log(u.name);  // Ada
console.log(u.role);  // member

3. Namespaces vs ES Modules (Prefer Modules)

Today, ES modules are the right default. A module is a file that uses import/export; each file has its own scope, and bundlers can tree-shake away unused exports. Namespaces predate modules and put everything on one global object. Reserve namespaces for global declaration files (.d.ts) where modules aren't available.

// PREFER ES modules in modern code. The same grouping as a namespace, but
// each FILE is its own scope and bundlers/tree-shaking understand it.

// geometry.ts
export const PI = 3.14159;
export function areaOfCircle(r: number): number { return PI * r * r; }

// main.ts
import { areaOfCircle, PI } from "./geometry";
areaOfCircle(2);

// Namespaces still legitimately appear in GLOBAL .d.ts files, e.g.:
//   declare namespace NodeJS { interface ProcessEnv { API_URL: string } }
// and in older script-tag (non-module) codebases. For app code: use modules.
// You can't 'import' in this single-file runner, so here is the RUNTIME
// difference. A namespace = one shared object built by an IIFE (everything in
// one global). A module = separate scopes you wire together with import/export.

// Namespace-style (one global object holding everything):
var Geometry;
(function (Geometry) {
  Geometry.areaOfCircle = (r) => 3.14159 * r * r;
})(Geometry || (Geometry = {}));
console.log(Geometry.areaOfCircle(2).toFixed(2)); // 12.57

// Module-style (conceptually): each file exports; the bundler only includes
// what's imported (tree-shaking). Simulated here as a plain exports object:
const geometryModule = { areaOfCircle: (r) => 3.14159 * r * r };
const { areaOfCircle } = geometryModule;          // like: import { areaOfCircle }
console.log(areaOfCircle(2).toFixed(2));           // 12.57

🎯 Your Turn

Hand-compile a MathUtils namespace with two exported helpers. Fill in the two blanks marked ___, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
// Recreate the compiled output of a 'MathUtils' namespace with two exports.

var MathUtils;
(function (MathUtils) {
  MathUtils.double = (n) => n * 2;
  MathUtils.triple = (n) => n * ___;   // 👉 triple multiplies by 3
})(MathUtils || (MathUtils = {}));

console.log(MathUtils.double(5));  // 10
console.log(MathUtils.___(5));     // 👉 call 'triple' -> 15

// ✅ Expected output:
//    10
//    15

Common Errors (and the fix)

Pro Tips

📚 Keep Learning

Mini-Challenge: Hand-Compile a Validation Namespace

No blanks this time — just a brief and a starting outline. Build the IIFE yourself, run it, and check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: hand-compile a 'Validation' namespace.
// 1. Build an IIFE that creates a global object 'Validation'.
// 2. Export isEmail(s)  -> true when s contains "@".
// 3. Export isEmpty(s)  -> true when s.length === 0.
// 4. Log Validation.isEmail("a@b.com"), Validation.isEmail("nope"),
//    and Validation.isEmpty("").
//
// ✅ Expected output:
//    true
//    false
//    true

// your code here

🎉 Lesson Complete

Practice quiz

What is a TypeScript namespace?

  • A runtime database
  • A type of variable
  • A way to group related code under one named object
  • A CSS scope

Answer: A way to group related code under one named object. A namespace groups related code under a single named object so it does not pollute the global scope.

What does a namespace compile to?

  • An IIFE that builds one object and attaches exported members
  • A class
  • A separate file
  • Nothing at all

Answer: An IIFE that builds one object and attaches exported members. A namespace compiles to an immediately-invoked function expression that builds one object.

Why is only an exported member visible outside the namespace?

  • Exported members run first
  • Non-exported members are deleted
  • It is random
  • The compiled IIFE only attaches exported members to the object

Answer: The compiled IIFE only attaches exported members to the object. Un-exported members stay private inside the IIFE's closure; export moves them to the public object.

What does nesting namespaces produce, like App.Models.User?

  • A single flat object
  • Nested objects: App has a Models property holding the helpers
  • An array
  • A union type

Answer: Nested objects: App has a Models property holding the helpers. Nested namespaces compile to nested objects forming a dotted access path.

Which should you prefer for modern application code?

  • ES modules (import/export)
  • Namespaces
  • Global variables
  • Script tags only

Answer: ES modules (import/export). ES modules give each file its own scope and work with bundlers and tree-shaking.

Where do namespaces still legitimately appear?

  • In every React component
  • In package.json
  • In global declaration (.d.ts) files
  • In CSS files

Answer: In global declaration (.d.ts) files. Namespaces are still used in global .d.ts files, e.g. declare namespace NodeJS { ... }.

How do you shorten a long namespace path?

  • With export default
  • With an import alias like import Models = App.Models
  • By deleting levels
  • With typeof

Answer: With an import alias like import Models = App.Models. import Alias = App.Models creates a shorthand for a deep namespace path.

If a file already has top-level import/export, do you usually need a namespace?

  • Yes, always
  • Only with classes
  • Only in strict mode
  • No, the file is already a module with its own scope

Answer: No, the file is already a module with its own scope. A file with import/export is already a module, so you almost never need a namespace inside it.

What happens to a namespace member declared without export?

  • It becomes a global
  • It stays private inside the closure, invisible outside
  • It is exported automatically
  • It throws an error

Answer: It stays private inside the closure, invisible outside. Without export it is a private function or variable trapped inside the IIFE.

A namespace can merge with which of these of the same name?

  • A string literal
  • An import statement
  • A class or function
  • A number

Answer: A class or function. A namespace merges with a class or function of the same name to add nested types or static helpers.

Continue this course

Frequently asked questions

What is a TypeScript namespace?

A namespace is a way to group related code (types, functions, constants) under a single named object so they don't pollute the global scope. In source you write 'namespace Foo { export ... }'; at compile time it becomes an IIFE that builds one object 'Foo' and attaches the exported members to it.

Namespaces vs ES modules — which should I use?

Use ES modules (import/export across files) for virtually all modern application code. Modules give each file its own scope, work with bundlers and tree-shaking, and match how the rest of the JavaScript ecosystem works. Namespaces are mostly a legacy feature from before modules existed in TypeScript.

Why does only an 'export'-ed member show up outside the namespace?

Because the compiled IIFE only attaches exported members to the namespace object. Anything without 'export' becomes a private function or variable inside the IIFE's closure — useful for helpers you don't want to expose, but invisible to code outside.

Where do namespaces still legitimately appear?

Chiefly in global declaration files (.d.ts), where you can't use modules — for example 'declare namespace NodeJS { interface ProcessEnv { ... } }' or the global Express namespace. They also show up in older script-tag codebases that load TypeScript output directly without a module system.