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.
Table of Contents
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
- One concept per file — a type and the functions that operate on it can share a file; two unrelated features should not.
- An index barrel per folder — re-export the public API so imports stay short and only intended symbols are reachable.
- Mark type-only imports — use
import typeso nothing unnecessary reaches the bundle. - Keep dependency direction one way — shared types at the bottom, feature code above them.
// 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.

