TypeScript Namespaces

A namespace groups related code — functions, classes, interfaces, and variables — under one named container. Everything inside the namespace lives in its own scope, preventing names from colliding with identifiers in other parts of the application. Namespaces were TypeScript's original module system and remain useful for organizing global code, declaration files, and large legacy projects.

The Naming Collision Problem

  Without namespaces — same name causes conflict:
  +---------------------------------------+
  | function validate() { /* email */ }   |  ← one validate
  | function validate() { /* phone */ }   |  ← Error: duplicate!
  +---------------------------------------+

  With namespaces — same name, different containers:
  +-------------------------------+  +------------------------------+
  | namespace EmailUtils {       |  | namespace PhoneUtils {       |
  |   function validate() { }   |  |   function validate() { }   |
  | }                            |  | }                            |
  +-------------------------------+  +------------------------------+
  EmailUtils.validate()              PhoneUtils.validate()  ← No conflict

Defining a Namespace

namespace MathHelpers {
    export function add(a: number, b: number): number {
        return a + b;
    }

    export function multiply(a: number, b: number): number {
        return a * b;
    }

    export const PI: number = 3.14159;

    // This is private — not exported, not accessible outside
    function internalHelper(): void {
        console.log("Internal only");
    }
}

console.log(MathHelpers.add(4, 6));       // 10
console.log(MathHelpers.multiply(3, 7));  // 21
console.log(MathHelpers.PI);              // 3.14159

Only members marked with export inside a namespace are accessible from outside. Non-exported members remain private to the namespace.

Namespace with Interfaces and Types

namespace Shapes {
    export interface Circle {
        kind: "circle";
        radius: number;
    }

    export interface Rectangle {
        kind: "rectangle";
        width: number;
        height: number;
    }

    export type Shape = Circle | Rectangle;

    export function getArea(shape: Shape): number {
        if (shape.kind === "circle") {
            return Math.PI * shape.radius * shape.radius;
        }
        return shape.width * shape.height;
    }
}

let c: Shapes.Circle = { kind: "circle", radius: 5 };
let r: Shapes.Rectangle = { kind: "rectangle", width: 4, height: 6 };

console.log(Shapes.getArea(c).toFixed(2)); // 78.54
console.log(Shapes.getArea(r));            // 24

Nested Namespaces

A namespace can contain another namespace, creating a hierarchy that mirrors a real-world domain structure.

namespace eStudy {
    export namespace Courses {
        export function getAll(): string[] {
            return ["TypeScript", "React", "Node.js"];
        }

        export function getById(id: number): string {
            return "Course #" + id;
        }
    }

    export namespace Students {
        export function getCount(): number {
            return 1500;
        }

        export function getTopStudent(): string {
            return "Ananya Sharma";
        }
    }
}

console.log(eStudy.Courses.getAll());         // ["TypeScript", "React", "Node.js"]
console.log(eStudy.Students.getCount());      // 1500
console.log(eStudy.Students.getTopStudent()); // Ananya Sharma

Namespace Aliases

When a namespace path is deeply nested, an alias shortens the access path. Use the import keyword (not the same as the module import) to create the alias.

namespace Company.Department.Engineering.Frontend {
    export function deploy(): void {
        console.log("Frontend deployed!");
    }
}

// Long path — hard to type repeatedly
Company.Department.Engineering.Frontend.deploy();

// Alias — much shorter
import Frontend = Company.Department.Engineering.Frontend;
Frontend.deploy(); // Frontend deployed!

Splitting Namespaces Across Files

A namespace can span multiple files. TypeScript merges all declarations with the same namespace name into one unified namespace. A reference directive at the top of each file tells TypeScript about the other file.

// shapes-circle.ts
namespace Geometry {
    export class Circle {
        constructor(public radius: number) {}
        area(): number { return Math.PI * this.radius * this.radius; }
    }
}
// shapes-rectangle.ts
namespace Geometry {
    export class Rectangle {
        constructor(public width: number, public height: number) {}
        area(): number { return this.width * this.height; }
    }
}
// app.ts
/// <reference path="shapes-circle.ts" />
/// <reference path="shapes-rectangle.ts" />

let circle = new Geometry.Circle(7);
let rect   = new Geometry.Rectangle(5, 10);

console.log(circle.area().toFixed(2)); // 153.94
console.log(rect.area());              // 50

Ambient Namespaces (Declaration Files)

Ambient namespaces describe the shape of existing JavaScript libraries that have no TypeScript definitions. The declare keyword tells TypeScript the code exists at runtime without providing an implementation.

// typings/jquery.d.ts
declare namespace $ {
    function ajax(url: string, settings?: object): void;
    function get(url: string): void;
    function post(url: string, data: object): void;
}

// Now TypeScript understands jQuery
$.ajax("/api/users");
$.get("/api/courses");

Namespace vs Modules

FeatureNamespaceES Module
File dependencyUses reference directives or compiles into one fileUses import / export
Global scopeAdds to global namespaceIsolated — no global pollution
Best forBrowser globals, legacy code, declaration filesModern projects with a module bundler
Tool supportOlder build systemsWebpack, Vite, esbuild
RecommendationUse for .d.ts files onlyPreferred for all new projects

Practical Example

// Validation namespace
namespace Validator {
    export interface Rule {
        test(value: string): boolean;
        message: string;
    }

    export function validate(value: string, rules: Rule[]): string[] {
        let errors: string[] = [];
        for (let rule of rules) {
            if (!rule.test(value)) {
                errors.push(rule.message);
            }
        }
        return errors;
    }

    export namespace Rules {
        export const required: Rule = {
            test: (v) => v.trim().length > 0,
            message: "This field is required."
        };

        export const email: Rule = {
            test: (v) => /\S+@\S+\.\S+/.test(v),
            message: "Enter a valid email address."
        };

        export const minLength = (n: number): Rule => ({
            test: (v) => v.length >= n,
            message: "Must be at least " + n + " characters."
        });
    }
}

let emailErrors = Validator.validate("", [
    Validator.Rules.required,
    Validator.Rules.email
]);

console.log(emailErrors);
// ["This field is required.", "Enter a valid email address."]

let passwordErrors = Validator.validate("abc", [
    Validator.Rules.required,
    Validator.Rules.minLength(8)
]);

console.log(passwordErrors);
// ["Must be at least 8 characters."]

Leave a Comment