Dividing the Work
As programs grow, keeping everything in one place becomes unmanageable. Modular decomposition splits a program into modules — self-contained units (files, packages, or groups of functions), each responsible for one coherent part of the job. A module exposes a small public interface (what others may use) and keeps the rest private (its internal details).
The two guiding principles are the same as for structured design, applied at a larger scale: each module should have high cohesion (one clear responsibility) and modules should have low coupling (minimal, well-defined dependencies between them). A well-decomposed program reads like a set of cooperating specialists rather than one sprawling monolith.
Good modules are reusable. A module that solves a general problem cleanly — a date library, a math utility, a data parser — can be used across many programs. Reuse avoids re-solving solved problems and concentrates bug-fixing and improvement in one place. The DRY principle ("Don't Repeat Yourself") captures the everyday version: when the same logic appears in several places, factor it into one module or function, so a change is made once, not scattered.
The discipline also supports parallel development: with clear module boundaries, different people can work on different modules at once, meeting only at the interfaces.
Common pitfall: copy-pasting the same block of logic into many places instead of factoring it into one reusable unit (violating DRY). Duplicated logic means every fix or change must be made in every copy — and the copies inevitably drift apart, becoming a source of subtle, inconsistent bugs.
Reuse by Extension
A module is reused by being called. Inheritance offers a second route: a subclass declares that it is a kind of some superclass, inherits its fields and methods, and then adds or replaces only what differs. A SavingsAccount extending Account gets the balance handling for nothing and writes down just what makes it a savings account.
The payoff is polymorphism. Because every subclass honours the superclass's interface, code written against that interface works on all of them at once: a loop that calls applyMonthly() on each Account in a list runs a different body for each element, selected at run time from the object's actual class. That selection is dynamic dispatch, and it is what allows a new subclass to be added without editing any of the code that uses it.
For this to be safe, a subclass must genuinely work wherever the superclass was expected. One that demands more of its callers, or promises them less, silently breaks code that was correct against the superclass. This is the substitution principle, and it is a design obligation — the compiler checks that the method signatures line up, not that the behaviour does.
Many languages separate the two jobs. An interface declares a contract with no implementation at all, and one class may implement several; inheritance then carries shared code while interfaces carry the promise.
Common pitfall: reaching for inheritance whenever two classes happen to share code. Inheritance asserts "is a kind of", and it welds the subclass to its parent's internals, so a change to the parent can break children that never mentioned the changed part. When the true relationship is "has a" or "uses a", composition — holding the other object as a field and delegating to it — reuses exactly the same code without inheriting the coupling.