Quiz

How do you organize your code?

Do you use module pattern, classical inheritance, something else?
Topics
JavaScript

TL;DR

Organize code around product capabilities and dependency boundaries, not one universal folder template. Give each module a small public API, keep volatile infrastructure behind adapters, colocate code that changes together, and make dependency direction explicit. Prefer composition and plain functions/objects; use classes and inheritance only when an actual subtype relationship and shared contract make them clearer.

Start simple and refactor when change patterns reveal a boundary. Deep “controllers/services/utils/helpers” layers can scatter one feature across the repository just as easily as one giant file can couple everything together.


A feature-oriented shape

For an application with checkout and account capabilities:

src/
├── app/
│ ├── start-app.js
│ └── routes.js
├── checkout/
│ ├── calculate-total.js
│ ├── checkout-controller.js
│ ├── checkout-view.js
│ ├── checkout.test.js
│ └── index.js
├── account/
│ ├── load-profile.js
│ ├── profile-view.js
│ └── index.js
└── infrastructure/
├── http-client.js
└── storage.js

checkout/index.js can export only the entry points other features are allowed to use. Internal helpers remain replaceable without every caller importing their file paths.

Separate policy from platform details

Keep business rules independent from fetch, DOM, storage, and frameworks where practical:

export function calculateTotal(items, discountPolicy) {
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0,
);
return discountPolicy(subtotal);
}

An adapter can load items from HTTP and pass them to this function. That makes the rule testable without implying that every collaborator must be mocked or split into its own class.

Choose abstractions from the problem

  • ES modules define file-level public and private bindings and are the default code-sharing mechanism.
  • Functions and composition fit transformations, policies, and injected behavior.
  • Classes fit objects with identity, invariants, and a meaningful lifecycle.
  • Inheritance fits substitutable types with a stable “is-a” relationship; composition is usually safer for optional behavior.
  • Events or observers fit one-to-many notifications, but require ownership, ordering, error, and cleanup rules.

Do not add a pattern because its name sounds architectural. State the problem it solves and the cost it introduces.

Practical rules

  • Colocate tests, styles, schemas, and small helpers with the feature they verify or support.
  • Keep shared code narrowly named. A growing utils directory often hides unrelated dependencies.
  • Prevent circular dependencies and avoid importing another feature's internals.
  • Validate data at external boundaries and expose domain-shaped values internally.
  • Keep configuration and secrets outside browser bundles; module privacy is not a security boundary.
  • Document architectural decisions and public APIs, not line-by-line behavior already visible in code.
  • Let formatting, linting, type checking, and tests enforce mechanical conventions.

As the system grows, use dependency rules or workspace/package boundaries where they prevent demonstrated coupling. Splitting code into packages too early adds publishing, versioning, and tooling overhead.

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise
Check your understanding Exercise

A small product has one checkout file. As payment providers and delivery rules grow, explain how you would introduce module boundaries without defaulting to generic controllers, services, and utils folders.