The Developer's Quarterly · TypeScriptVol. III · March 2026

@typescript-eslint/no-namespace:Why It Exists and Why It Is Usually Right

The rule exists because namespaces are usually a worse default than ES modules, even though they still have a narrow place in legacy code and declaration files.

If you have worked in TypeScript for any length of time, you have probably seen the lint rule @typescript-eslint/no-namespace and wondered why it exists at all. Namespaces are a valid TypeScript feature, so why does a modern lint rule push you away from them?

The short answer is that namespaces solve a problem TypeScript no longer wants most application code to have. In 2026, the default answer for organizing code is modules, not namespaces. Modules align with JavaScript, with bundlers, with the Node.js runtime, and with how most teams structure front-end and back-end projects.

What a namespace actually is

A TypeScript namespace is an internal module pattern that groups related values under a single object-like name. It looks tidy, especially if you come from older codebases.

Namespace example
namespace Geometry { export const pi = 3.14159; export function circleArea(radius: number) { return pi * radius * radius; } } console.log(Geometry.circleArea(10));

That works, but it comes with a hidden cost: TypeScript has to emit extra JavaScript to make the namespace exist at runtime. That means you are not just organizing types; you are changing the shape of the output.

Why the rule exists

The rule exists because namespaces are a bad default in modern TypeScript for a few concrete reasons.

1 ES modules are the standard

Modern JavaScript already has a built-in module system, so file-based exports and imports usually solve the problem more cleanly.

2 Namespaces encourage global coupling

It becomes too easy to create one shared container that any file can read or mutate.

3 They add a TypeScript-only abstraction

For most app code, the extra mental model is unnecessary because bundlers and modules already handle the organization problem.

The better default: modules and named exports

Here is what the same idea looks like when written the modern way.

Module-based alternative
// math.ts export function clamp(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); } export function percentOf(value: number, total: number) { return total === 0 ? 0 : (value / total) * 100; }
Importing the functions
// dashboard.ts import { clamp, percentOf } from './math'; const progress = clamp(percentOf(42, 50), 0, 100);

This version is more explicit, easier to test, and easier to tree shake. It also matches how the rest of the JavaScript ecosystem works.

A practical comparison

Approach What it organizes Best fit
Namespace Types and values under one synthetic name Legacy code, ambient declarations
ES module Exports from a file Modern application code
declare namespace Type-only shape for global libraries .d.ts files, third-party globals

When namespaces are still acceptable

The rule is not saying namespaces are morally wrong. It is saying they should be rare. There are still a few valid cases.

Ambient declarations Use declare namespace when you are describing a global library that already exists at runtime.
Declaration merging Namespaces can still appear in .d.ts files when you are augmenting older libraries.
Very old codebases Namespaces may be the least risky option while you migrate a pre-module project.
Temporary transitions A lint override can be reasonable if you are actively moving toward modules.

What the lint rule is protecting you from

1 Hidden runtime structure

Namespaces can generate runtime objects that are harder to optimize and reason about than plain imports.

2 Global-state habits

They make it too easy to centralize unrelated code behind one shared name.

3 Weaker module boundaries

File-based exports give you natural boundaries that namespace-style organization usually blurs.

A migration example

If you inherit namespace-heavy code, the fix is usually not a rewrite. Start by extracting values into modules and re-exporting them from a single file if you still want a grouped API.

Before
namespace Formatting { export function titleCase(input: string) { return input .split(' ') .map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase()) .join(' '); } }
After
export function titleCase(input: string) { return input .split(' ') .map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase()) .join(' '); }

If you still want a namespaced-looking surface, you can create an object explicitly.

Explicit grouping
import { titleCase } from './after'; export const Formatting = { titleCase, };

Rule of thumb

Use modules first If the code is part of an application or package, prefer exports and imports.
Use namespaces sparingly Reserve them for legacy code, globals, or declaration files.
Avoid namespace architecture Do not use namespaces as the main way to organize modern code.
Prefer explicit dependencies Imports communicate better than shared global containers.

@typescript-eslint/no-namespace exists because the ecosystem moved on from namespaces as an application pattern, and the rule helps keep TypeScript code aligned with modern JavaScript.

In short, the rule is not overreach. It is setting a better default.