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 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.
// 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;
}
// 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.
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.
namespace Formatting {
export function titleCase(input: string) {
return input
.split(' ')
.map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase())
.join(' ');
}
}
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.
import { titleCase } from './after';
export const Formatting = {
titleCase,
};
Rule of thumb
@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.