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
| Feature | Namespace | ES Module |
|---|---|---|
| File dependency | Uses reference directives or compiles into one file | Uses import / export |
| Global scope | Adds to global namespace | Isolated — no global pollution |
| Best for | Browser globals, legacy code, declaration files | Modern projects with a module bundler |
| Tool support | Older build systems | Webpack, Vite, esbuild |
| Recommendation | Use for .d.ts files only | Preferred 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."]
