Pular para o conteúdo principal
The zero-to-master on-ramp — Git, the command line, a systems language, and the tooling every engineer uses daily.

Foundations & Tooling

The zero-to-master on-ramp — Git, the command line, a systems language, and the tooling every engineer uses daily.

Programming Paradigms (OOP, FP, Concurrent)

A programming paradigm is a way of structuring a program around a small set of organising principles: in OOP, objects and messages; in FP, pure functions and immutable data; in concurrent programming, multiple flows of control that happen at once. No single paradigm dominates; each is a lens for a different class of problem, and almost every modern language is multi-paradigm — the engineer’s choice is which paradigm to lean on for which piece.

This topic gives you the vocabulary to make the choice deliberately.

Three Paradigms Through Three Questions

Each paradigm answers a different organising question:

ParadigmQuestion it organises the answer to
Object-Oriented“Who is responsible for which piece of state, and what is the protocol for asking them to act?”
Functional“How do we compute new values from old values without modifying anything?”
Concurrent“How do multiple independent flows of control coordinate without corrupting shared state?”

The three are not exclusive. A modern backend program has all three: a domain model in OOP shape (entities with encapsulated state and well-defined operations), the data-transformation pipeline in FP shape (stateless parallel map/filter/reduce), and the request handling in concurrent shape (threads or coroutines coordinating to serve many users).

Object-Oriented Programming

The three principles, restated in a way that actually distinguishes good code from cargo-cult:

PrincipleWhat it actually meansCommon corruption
EncapsulationThe object’s internal state is non-visible to callers; they speak to it via method calls“Getter/setter for every field” — this is a struct in disguise, not encapsulation
InheritanceSubstitutability: a subclass instance can stand in for a parent instance anywhereHierarchies 6 levels deep where descendants violate parent contracts; “code reuse” masquerading as subtyping
PolymorphismThe same call name resolves to different implementations based on the receiverif (type == X) ladders everywhere — the polymorphism is in the type-check, not the architecture

The essence of OOP is decision deferral — when a caller invokes shape.area(), the caller does not know which shape will be in the variable at runtime; the language defers the decision to dispatch. Where to use OOP well: domain models with clear responsibilities and operations that operate on state that wants to be hidden. Where OOP hurts: data pipelines where there’s no state to hide and no behaviour to defer.

Functional Programming

FP organises computation around pure functions — functions whose output depends only on their input and which have no side effects. The two consequences:

  • Immutability — data is not modified; new data is returned. users.filter(active) returns a new list, not a mutated one.
  • Referential transparency — a function call can be replaced by its return value without changing program behaviour. Optimisation (memoisation, parallel evaluation, dead-code elimination) becomes a compiler concern.
FP shapePractical use
map, filter, reduceData transformation pipelines — the foundation of modern data engineering
Higher-order functions (functions taking or returning functions)Strategy abstraction (see craft-design-patterns); effect systems in FP languages
Lazy evaluationProcess streams of any size without buffering; only evaluate what’s needed
Algebraic data types + pattern matchingClosed enumerations of shapes; exhaustiveness-checked dispatch (Rust enums, Scala enums)

Where FP wins: data transformation pipelines, parallel pipelines (no shared state, no races), compiler passes, configuration-as-data. Where FP hurts: problems where the whole point is shared mutable state (a database engine, an interactive UI’s session state) — FP forces you to model state explicitly, which is more work than Java’s class members but easier to reason about.

The discipline to internalise: prefer immutability, default to immutability, accept mutation only where profiling shows it cost. This is roughly how Rust, modern C++, and modern JavaScript disciplinaires work today.

Concurrent Programming

Concurrent programming organises multiple flows of control that execute simultaneously. The central problem — and the choice that splits the paradigm — is how those flows share state.

ApproachMechanismLanguagesTrade-off
Shared-state concurrencyMultiple threads + locks / atomics / mutexesC, C++, Java (traditional), RustFast, but the engineer proves correctness: deadlock, race, hazard all must be coded around
Message-passingIndependent actors with mailboxes; messages, not shared referencesErlang, Akka (JVM), Go’s goroutines+channelsEasier to reason about (no shared pointers), but the programmer must think in protocols
Software transactional memory (STM)Memory is transactional — like a database, with committed reads/writesClojure, HaskellComposable and deadlock-free; runtime overhead, with contention management
Async/await + cooperative schedulingCoroutines yield control at await points; one thread per (logical) coreJavaScript (Node, browser), Python (asyncio), Rust (async), C#Single-threaded by default, no shared-state races; flow-control inversion traps await

The choice is rarely which one; each language’s run-time picks one or two and commits. The engineer’s job is understanding which the runtime chose, and writing the code in that style.

RuntimeDefault concurrency model
JVM (Java)Shared state, locks. synchronized for legacy code; Lock / ConcurrentHashMap for new.
GoGoroutines + channels (message-passing).
Erlang / ElixirProcesses with mailboxes; “let it crash” supervision trees.
Node.jsSingle-threaded event loop; async/await cooperatively.
RustShared-state with the borrow-checker preventing data races at compile time; async/await is cooperative.

Three universal truths apply regardless of model:

  1. Shared mutable state is the root of concurrency bugs. All three other models eliminate some class of it.
  2. Backpressure is the engineering problem in every concurrency model. A queue grows too fast → memory blowup; a channel buffers → backpressure up the chain.
  3. The fine-grained test is in the code under load, not in clean architecture review. Patterns that look correct can deadlock under specific scheduling.

Multi-Paradigm as the Engineering Reality

No production codebase is “pure” anything. The practical engineer reaches for the paradigm that fits the problem:

SubsystemLikely dominant paradigmWhy
Domain entities (bank account, order, user)OOPEncapsulated state with well-defined operations; polymorphic dispatch
ETL / data pipelineFPPure transformations over immutable streams
Request handling + worker poolConcurrentMultiple flows coordinating
Iterators, callbacks, configuration injectionHigher-order functionsStrategy abstraction without ceremony
Capability-sensitive code (parser, cmd dispatch)Algebraic data types + pattern matchingClosed enumeration + exhaustive dispatch

The rule of thumb — borrow from each where it makes the next 6 months easier, not where it makes the next 6 weeks fashionable.

Practice Trajectory

  1. Pick a piece of your own code that feels heavy. Identify whether it is fighting because it’s OOP-shaped code forced to be FP-shaped, or the opposite. The feeling of friction is the diagnosis.
  2. Take a single function and reimplement it three ways: OOP (with a class), FP (with a higher-order map), and “pure, no abstraction”. Compare readability on a six-month reread.
  3. Identify one piece of shared mutable state in a system you work on. Apply message-passing: convert the state to an actor with a mailbox. Note what became easier; note what became heavier.
  4. Take a long class with 6+ methods on the same instance state. Refactor to a module of pure functions that take the state as input and return the mutated state. Profile the difference in testability.
  5. Audit one async/await codebase for the absence of structured concurrency — orphaned tasks that escape their parents. Most async codebases leak here.

When It’s the Right Tool

SituationTakeaway
Domain entities with encapsulated state that the team changes togetherOOP with single-responsibility classes
Data transformation, parsing, compilation passesFP — pure pipelines over immutable data
Multi-user server with shared stateConcurrent — explicit concurrency model (shared state + locks, or message-passing)
Closed set of cases (`successerror
“We must pick one paradigm for the codebase”No — a multi-paradigm language (Rust, Scala, modern Python, modern JavaScript) supports all three; the engineer’s discipline is picking per-subsystem