TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
47: Modules and Namespaces
If you've spent any time reading older TypeScript codebases—or perhaps some legacy C# projects—you've probably seen the namespace keyword. It looks organized. It feels like you're creating a neat little bucket to hold all your related logic and keep the global scope from becoming a disaster. For a while, this was the standard way to organize TypeScript code, but in modern development, it's almost always the wrong choice.
The lure of the "big bucket"
Let's say we're building a payment processing system. You have logic for handling credit cards, PayPal, and some internal validation rules. The naive approach is to wrap everything in a namespace to avoid clashing with other parts of the app. It looks something like this:
namespace PaymentProcessor {
export class Validator {
validate(card: string) {
return card.length === 16;
}
}
export class Gateway {
process(amount: number) {
console.log(`Processing $${amount}...`);
}
}
}
// Usage
const val = new PaymentProcessor.Validator();
At first glance, this seems great. You've got a clear hierarchy. But here is where it breaks: namespace is a TypeScript-specific construct. It doesn't exist in JavaScript. When the compiler runs, it transforms that namespace into a giant object attached to the global scope (or a shared object). If you have five different files all using namespace PaymentProcessor, you're relying on a "global" state that is incredibly hard to track. You lose the ability to tell exactly where a piece of code is coming from without hunting through files, and you're basically inviting name collisions if another library happens to use the same namespace name.
Letting the file system do the work
The better way—and the way we do things in every professional TS project I've touched in the last few years—is using ES Modules. Instead of creating an artificial container inside your code, you treat the file itself as the container. You don't need a namespace keyword because the module system handles the isolation for you.
I'd split that payment logic into separate files. In validator.ts, I just export the class. In gateway.ts, I do the same. Then, in my main logic, I import exactly what I need:
// validator.ts
export class Validator {
validate(card: string) {
return card.length === 16;
}
}
// gateway.ts
export class Gateway {
process(amount: number) {
console.log(`Processing $${amount}...`);
}
}
// main.ts
import { Validator } from './validator';
import { Gateway } from './gateway';
const val = new Validator();
This approach is fundamentally different because it's explicit. When I look at the top of main.ts, I know exactly where Validator is coming from. I'm not guessing which global namespace it belongs to.
The cost of the transition
You might wonder why anyone would ever use namespaces if modules are so much better. To be fair, namespaces are slightly easier to set up if you're just dropping a single .js file into an HTML page with a <script> tag. But we aren't doing that anymore. We use bundlers like Vite, Webpack, or esbuild. These tools are designed specifically to optimize ES modules. They can perform "tree-shaking," which means if you import a huge library but only use one function, the bundler can throw away the rest of the code to keep your bundle size small.
Namespaces kill tree-shaking. Because a namespace compiles to a single object, the bundler usually can't tell which parts of that object are actually being used, so it includes the whole thing. By moving to modules, you're not just making your code more readable; you're making your application faster for the end user.
📋 Practical Task
Refactoring the Legacy Order-Management System
You've inherited a legacy file called orders.ts that uses namespaces. Your goal is to modernize this into a module-based structure. The current code is:
namespace OrderManagement {
export interface Order {
id: string;
total: number;
}
export class OrderCalculator {
calculateTax(total: number) {
return total * 0.15;
}
}
export class OrderLogger {
logOrder(order: Order) {
console.log(`Order ${order.id} logged.`);
}
}
}
Your Task:
- Break this single file into three separate files:
types.ts(for the Order interface),calculator.ts(for the OrderCalculator), andlogger.ts(for the OrderLogger). - Remove all
namespacedeclarations. - Use
exportkeywords to make the classes and interface available. - Create a fourth file,
index.ts, that imports all three elements and demonstrates them working together (e.g., creating an order, calculating tax, and logging the order).
There are no comments for now.