Site icon Ampersand Tutorials

TypeScript Modules and Namespaces (2026)

Quick answer: TypeScript modules follow the standard ES module format — one file, explicit import and export — and that is what you should use for application code. Namespaces are a legacy TypeScript construct for grouping code without a module system; the old module Foo {} syntax has been renamed to namespace Foo {} and namespaces are now discouraged for new code. Use import type for type-only imports.The sections below cover TypeScript modules in practice: import type, why TypeScript modules replaced namespaces, and how to organise a project so the module graph stays readable.

ES modules are the default

A TypeScript file with a top-level import or export is a module, and it gets its own scope. Everything works exactly as in the JavaScript modules guide, with types layered on.

// models/order.ts
export interface Order { id: number; total: number; }

export function applyTax(order: Order, rate = 0.2): Order {
  return { ...order, total: order.total * (1 + rate) };
}

// checkout.ts
import { applyTax, type Order } from "./models/order";

const order: Order = { id: 1, total: 40 };
console.log(applyTax(order));

import type keeps types out of the bundle

An import that exists only for typing should be marked so the compiler erases it entirely. This matters for bundle size and for avoiding a runtime import of a module you only needed a type from.

import { type ApiResponse } from "./api";        // inline marker
import type { User } from "./models/user";       // whole-statement form

export type { User };                            // re-export a type

With the compiler option verbatimModuleSyntax enabled — the default in current project templates — this distinction is enforced rather than optional. It makes the emitted JavaScript match what you wrote, so a type import can never accidentally become a real one.

Namespaces, and why TypeScript modules are better

Before ES modules were standard, TypeScript offered namespaces to group related code inside one global scope. They still compile, but they fight the toolchain: bundlers, tree-shaking and modern editors all expect modules.

// legacy style — avoid in new code
namespace Validation {
  export const isEmail = (s: string) => s.includes("@");
}

Validation.isEmail("a@b.com");

// modern equivalent — just use a module
// validation.ts
export const isEmail = (s: string) => s.includes("@");

One important syntax change: the old form module Foo { ... } to declare an internal namespace was removed in TypeScript 7. If you maintain older code, rename it to namespace Foo { ... }. Note that a different construct, the ambient declaration declare module "some-package", is unrelated and still valid — it describes the shape of an external library.

Organising a project around TypeScript modules

// models/index.ts
export type { User } from "./user";
export type { Order } from "./order";

// anywhere
import type { User, Order } from "./models";

Next: the tsconfig guide. To learn TypeScript properly with a mentor, Ampersand Academy teaches web development one-to-one.

Frequently asked questions

Should I use namespaces in TypeScript?

No, not in new code. Use standard ES modules with import and export. Namespaces predate modules, conflict with modern bundlers and are no longer recommended.

What does import type do?

It imports a symbol for typing only, so the compiler erases it and it never appears in the emitted JavaScript. This keeps type-only dependencies out of the bundle.

What is the difference between a module and a namespace?

A module is a file with its own scope that uses import and export. A namespace is a legacy way to group declarations in a shared scope without a module system.

Can I mix declare module with namespaces?

Yes. An ambient declaration such as declare module for a third-party package describes an external library and is still valid. It is unrelated to internal namespaces, which are discouraged.

What is a barrel file in TypeScript?

An index file that re-exports a folder’s public symbols so consumers import from one path. Keep them shallow to avoid accidental circular imports and slow builds.

Exit mobile version