← Back to TypeScript Course | Chapter 23: Performance and Best Practices | Lesson 7 of 7

Code Organization

Good TypeScript organization separates related responsibilities and makes dependencies easier to understand. Clear module boundaries, exports, and naming help projects remain maintainable as they grow.

Organize by Responsibility

Grouping files by feature or responsibility (e.g. a users/ folder with its types, logic, and tests together) scales better than grouping by file type, since related changes stay co-located instead of scattered across types/, services/, and components/ folders.

Example: Organize by Responsibility

typescript
// users/types.ts, users/service.ts, users/user.test.ts
// grouped together rather than scattered across types/, services/, tests/
console.log("Feature-based folders keep related files co-located");

Use Named Exports

Named exports make it clear at the import site exactly what's being pulled in and make refactoring safer, since renaming a default export doesn't trigger any warning at its call sites the way renaming a named export does.

Example: Use Named Exports

typescript
export function formatDate(date: Date): string {
  return date.toISOString();
}
export const APP_NAME = "MyApp";
console.log(formatDate(new Date()), APP_NAME);

Separate Types from Implementation When Useful

Separating a module's types into their own file makes sense once multiple files need to import just the shape without pulling in the implementation that produces it, avoiding unnecessary coupling.

Example: Separate Types from Implementation When Useful

typescript
// user-types.ts
interface User { id: number; name: string; }
// user-service.ts would import just the User type from user-types.ts
const u: User = { id: 1, name: "Ravi" };
console.log(u);

Avoid Circular Dependencies

Circular dependencies — where module A imports from module B which imports back from A — can cause values to be undefined at import time due to evaluation order, so structuring imports as a one-directional graph avoids a whole class of hard-to-debug bugs.

Example: Avoid Circular Dependencies

typescript
// a.ts imports from b.ts, b.ts imports from a.ts -> undefined at import time
// Fix: extract shared code into a third module both can import from.
console.log("Structure imports as a one-directional graph");

Keep Entry Points Small

Keeping an entry point file (like index.ts) limited to re-exports and wiring, rather than actual logic, makes it easy to see a module's full public surface at a glance without reading implementation details.

Example: Keep Entry Points Small

typescript
// index.ts
export { formatDate } from "./date-utils";
export { UserService } from "./user-service";
console.log("index.ts only re-exports, no real logic lives here");
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Using export default for everything, so importers rename things differently in each file.
  2. Creating circular imports between two modules, which can leave a value undefined when first used.
  3. Putting all types in one giant file, so every feature depends on everything else.
Chapter Summary
  • Avoid any and prefer unknown so values must be checked before use.
  • Use type narrowing and readonly consistently to keep code safe and predictable.
  • Choose between interfaces and type aliases deliberately, and follow naming conventions and clear code organization.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.