Programming Paradigms
Programming Paradigms
A paradigm is a mental model for structuring computation. The language you use constrains — but does not fully determine — which paradigm you employ. Understanding paradigms lets you write better code in any language and choose the right tool for each problem.
Imperative Programming
Imperative code describes how to do something: a sequence of statements that modify state. You loop, assign, branch. Assembly is purely imperative. C is largely imperative. Most JavaScript written by beginners is imperative.
The strength of imperative style is directness — the code closely mirrors what the CPU actually does. The weakness is that explicit state mutation creates complexity that grows non-linearly with program size. Tracking which variable holds what value at which point becomes the dominant cognitive load.
Object-Oriented Programming (OOP)
OOP organizes code around objects — encapsulated bundles of state and behavior. The four pillars:
Encapsulation hides internal state behind a public interface. Objects expose methods, not raw fields. This localizes the impact of implementation changes.
Inheritance allows a class to extend another, inheriting its methods and properties. This enables code reuse but creates tight coupling. Favor composition over inheritance is a well-earned maxim: a Car has an Engine is often cleaner than Car is a Vehicle when the hierarchy becomes deep.
Polymorphism lets code work with any object satisfying an interface, regardless of concrete type. You write a function accepting a Drawable — it works for circles, squares, or custom shapes. In statically typed languages, interfaces and generics formalize this. In dynamically typed languages, duck typing achieves the same result implicitly.
Abstraction is exposing essential features while hiding implementation detail. A List exposes append, remove, contains — not its internal array or linked list mechanics.
OOP is dominant in enterprise software because it models domains naturally (customers, orders, payments) and supports large teams working on separate classes. Its failure modes are deep inheritance hierarchies, god objects with too many responsibilities, and overengineered abstractions.
Functional Programming (FP)
Functional programming treats computation as the evaluation of mathematical functions. Its core principles:
Immutability: data structures are never modified after creation. Instead of mutating an array, you create a new one. This eliminates a huge class of bugs — race conditions, aliasing bugs, and unexpected side effects — at the cost of some performance (mitigated by structural sharing).
Pure functions: a function given the same inputs always returns the same output and has no observable side effects (no network calls, no mutations, no I/O). Pure functions are trivially testable, memoizable, and parallelizable.
Higher-order functions: functions that accept functions as arguments or return functions. map, filter, reduce are the canonical examples. Composition — chaining functions together — replaces loops.
Referential transparency: any expression can be replaced by its value without changing program behavior. This enables powerful compiler optimizations and equational reasoning about code.
Languages like Haskell and Elm enforce purity. JavaScript, Python, and Kotlin support a functional style alongside OOP. React's component model is heavily functional — components are functions from state to UI.
Declarative Programming
Declarative code describes what you want, not how to achieve it. SQL is the canonical example: SELECT name FROM users WHERE age > 18 expresses intent without specifying the join algorithm or index access pattern. HTML and CSS are declarative. React's JSX is declarative.
The compiler or runtime is responsible for finding an efficient implementation. This raises the level of abstraction and enables powerful optimization but sacrifices low-level control.
Logic Programming
Prolog, Datalog, and logic programming languages express programs as a set of facts and rules. The system uses unification and backtracking to find solutions. This is powerful for constraint satisfaction, theorem proving, and database query optimization — the query planner in a SQL database is essentially a logic programming system.
Choosing a Paradigm
In practice, modern systems blend paradigms. A backend API might use OOP for domain modeling, FP for data transformation pipelines, and declarative queries for database access.
The key question is which paradigm fits the problem structure:
- State machines and business processes → OOP or procedural
- Data transformation pipelines → FP
- Database queries or rule systems → declarative
- Concurrent systems → FP's immutability eliminates entire classes of concurrency bugs
The engineers who write the most maintainable systems are those who recognize which paradigm a given module calls for — and have the vocabulary to execute it fluently.