Skip to content

TypeScript Namespaces & Modules — Complete Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about TypeScript Namespaces & Modules. We cover key concepts, practical examples, and best practices to help you master this topic.

TypeScript gives you two ways to organize code — namespaces (internal modules) for grouping related code within a file or across files, and ES modules for external dependency management with standard import/export syntax.

What You'll Learn

  • Namespaces: syntax, nesting, multi-file
  • ES modules: import, export, re-export
  • Namespace vs module: when to use each
  • Ambient declarations with declare module
  • Module resolution and import type

Why It Matters

Understanding the difference between namespaces and modules is crucial for organizing TypeScript code correctly. Using namespaces when modules are expected (or vice versa) leads to broken builds, unexpected globals, and confused colleagues.

Real-World Use

The Doda Browser extension API uses namespaces for its internal organization (browser.tabs, browser.storage, browser.runtime) — these are all ambient namespace declarations that describe the browser API without runtime code. External libraries like React use ES modules.

Learning Path

flowchart LR
  A[Utility Types] --> B[Namespaces & Modules]
  B --> C[Declaration Files]
  B --> D[You Are Here]
  C --> E[Type Manipulation]
  E --> F[Classes & OOP]

Namespaces

Namespaces group related code under a single global name:

namespace Utilities {
  export function greet(name: string): string {
    return `Hello, ${name}!`;
  }

  export function capitalize(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  export const VERSION = "1.0.0";
}

// Usage
console.log(Utilities.greet("Alice"));  // Hello, Alice!
console.log(Utilities.VERSION);        // 1.0.0

Think of a namespace like a folder — it keeps related files together and prevents name collisions with code outside the folder.

Nested Namespaces

namespace App {
  export namespace Config {
    export const API_URL = "https://api.example.com";
    export const TIMEOUT = 5000;
  }

  export namespace Utils {
    export function formatDate(date: Date): string {
      return date.toISOString();
    }
  }
}

console.log(App.Config.API_URL);  // https://api.example.com
console.log(App.Utils.formatDate(new Date())); // 2026-06-28T...

Multi-File Namespaces

File: src/shapes.ts

namespace Shapes {
  export interface Shape {
    area(): number;
  }
}

File: src/circle.ts

/// <reference path="shapes.ts" />
namespace Shapes {
  export class Circle implements Shape {
    constructor(public radius: number) {}
    area(): number { return Math.PI * this.radius ** 2; }
  }
}

File: src/main.ts

/// <reference path="shapes.ts" />
/// <reference path="circle.ts" />
const circle = new Shapes.Circle(5);
console.log(circle.area()); // 78.54

The /// <reference> directive tells TypeScript to include the referenced file. This is the old-style approach — modern TypeScript prefers ES modules.

ES Modules

ES modules use import and export:

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

export const PI = 3.14159;
// main.ts
import { add, subtract, PI } from "./math.js";

console.log(add(5, 3));      // 8
console.log(PI);             // 3.14159

Default Exports

// logger.ts
export default class Logger {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

// main.ts
import Logger from "./logger.js";
const logger = new Logger();
logger.log("Hello");

Re-exporting

// index.ts — barrel file
export { add, subtract } from "./math.js";
export { default as Logger } from "./logger.js";
export * from "./types.js";

import type

Use import type when you only need the type, not the runtime value:

// types.ts
export interface User { name: string; age: number; }
export function createUser(name: string): User { return { name, age: 30 }; }

// main.ts
import type { User } from "./types.js"; // Only imports the type
import { createUser } from "./types.js"; // Only imports the runtime value

import type is erased during compilation — no runtime import is generated. This can reduce bundle size.

Namespace vs Module: When to Use Each

Aspect Namespace Module
Scope Global/ambient File-based
Syntax namespace X {} import/export
Bundle Single global Multiple files
Tree-shaking Not supported Supported
Legacy code Common in old TS, DefinitelyTyped Modern standard
Declaration files Ambient namespace declarations Module declarations

Rule of thumb: Use ES modules for most code. Use namespaces only for:

  • Declaring ambient types (like browser APIs)
  • Organizing code in non-module projects
  • Legacy code compatibility

Ambient Declarations

Ambient declarations describe types without providing implementation:

// globals.d.ts
declare namespace MyGlobalLib {
  function init(config: Record<string, unknown>): void;
  const VERSION: string;
}

// In any file, without importing:
MyGlobalLib.init({ debug: true });
console.log(MyGlobalLib.VERSION);

Ambient Module Declarations

// types/css.d.ts
declare module "*.css" {
  const content: Record<string, string>;
  export default content;
}

// types/env.d.ts
declare module "@env" {
  export const API_URL: string;
  export const NODE_ENV: "development" | "production";
}

Module Augmentation

You can add properties to existing modules:

// express.d.ts
import "express";

declare module "express" {
  interface Request {
    user?: {
      id: string;
      role: "admin" | "user";
    };
  }
}

Now in your route handlers:

import { Request, Response } from "express";

app.get("/profile", (req: Request, res: Response) => {
  console.log(req.user?.id); // Typed!
});

Common Mistakes

1. Using Namespaces with ES Modules

Namespaces create global variables. ES modules create local scopes. Don't mix them:

// Bad: namespace inside a module file
export namespace Utils {
  export function doStuff() {}
}

// Good: just export the function
export function doStuff() {}

2. Forgetting the .js Extension in Import Paths

TypeScript recommends using .js extensions in import paths, even for .ts files:

// Correct (even though it's a .ts file)
import { User } from "./types.js";

// Wrong (will cause issues with some bundlers)
import { User } from "./types";

3. Using import When You Mean import type

// Bad: imports the runtime value even though it's only used as a type
import { User } from "./types";

// Good: only imports the type
import type { User } from "./types";

4. Circular Dependencies

Module A imports from B, B imports from A. This leads to undefined values at runtime. Restructure to avoid cycles.

5. Not Using Barrel Files

Instead of importing from multiple files:

// Instead of:
import { User } from "./models/user";
import { Product } from "./models/product";
import { Order } from "./models/order";

// Create a barrel:
// models/index.ts
export { User } from "./user";
export { Product } from "./product";
export { Order } from "./order";

// Then:
import { User, Product, Order } from "./models/index.js";

6. Using export default on Named Exports

// Avoid default exports unless you have a single main export
// Bad: import Logger from "./logger"; // Can rename freely
// Good: import { Logger } from "./logger"; // Name must match

Practice Questions

  1. What is the difference between a namespace and a module? A namespace creates a global object grouping related code. A module creates a file-level scope using import/export.

  2. When should you use import type? When you only need the type, not the runtime value. It's erased during compilation, reducing bundle size.

  3. What is an ambient declaration? A declaration that describes types without providing implementation, typically in .d.ts files.

  4. What is module augmentation? Adding properties to an existing module's types without modifying the module itself.

Challenge: Create a barrel file for a utils directory that re-exports all utility functions. Then create an ambient declaration for a hypothetical window.__APP_CONFIG__ global variable.

FAQ

Can I use namespaces and modules together?

Yes, but carefully. Namespaces are best for ambient declarations. For runtime code, use modules.

What is `/// `?

A triple-slash directive that tells TypeScript to include the referenced file. It's the old-style approach, replaced by ES module imports.

Do I need `.js` extensions in imports?

Modern TypeScript recommends them. Some bundlers (Webpack, Vite) resolve without extensions, but native ESM requires .js.

What is a barrel file?

An index file that re-exports all exports from a directory, providing a single import path for consumers.

How do I write ambient declarations for a global library?

Create a .d.ts file with declare namespace or declare module blocks describing the library's types.

Mini Project: Modular Utility Library

// src/utils/math.ts
export function add(a: number, b: number): number { return a + b; }
export function multiply(a: number, b: number): number { return a * b; }

// src/utils/strings.ts
export function capitalize(s: string): string {
  return s.charAt(0).toUpperCase() + s.slice(1);
}
export function truncate(s: string, max: number): string {
  return s.length > max ? s.slice(0, max) + "..." : s;
}

// src/utils/index.ts — barrel
export { add, multiply } from "./math.js";
export { capitalize, truncate } from "./strings.js";

// src/index.ts
import { add, capitalize } from "./utils/index.js";

console.log(add(10, 20));         // 30
console.log(capitalize("hello")); // Hello

// Ambient declaration for global config
// src/types/global.d.ts
declare namespace AppConfig {
  export const API_BASE: string;
  export const VERSION: string;
  export function isProduction(): boolean;
}

What's Next

Now learn about declaration files for consuming JavaScript libraries:

Lesson Description
{{< ref "/programming-languages/typescript/15-utility-types" >}} Review utility types
{{< ref "/programming-languages/typescript/17-declaration-files" >}} .d.ts files, declare, DefinitelyTyped
{{< ref "/programming-languages/typescript/18-type-manipulation" >}} satisfies, branded types

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro