Keentune

Coding Patterns curriculum

21 chapters
·
215 concepts
·
free
Everything the adaptive question bank can teach and test in Coding Patterns, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Coding Patterns guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. What a pattern is, and when not to use one
a pattern is a named solution to a recurring problem in a stated context, not a reusable snippet
a pattern is copied into your design by hand, so its cost is comprehension; a framework hands you code and charges you a dependency
every pattern names the competing pressures it balances, and ignoring those forces is precisely how it gets misapplied
two patterns can share a class diagram and differ only in intent, so structure alone never identifies one
applying a pattern before the variation it absorbs actually exists buys indirection and no flexibility
creational patterns govern object creation, structural patterns composition, behavioral patterns communication
class patterns fix relationships at compile time through inheritance; object patterns compose at runtime and can be rewired
an idiom is language-specific and a pattern is not, which is why some GoF patterns vanish where functions are first-class
B. Creational patterns
a subclass decides which concrete product to instantiate, because the base class calls a creation method it does not implement
one interface creates a whole FAMILY of products that must be used together, so mismatched members cannot be mixed
Factory Method varies one product through inheritance; Abstract Factory varies a family through composition
separates step-by-step construction from the finished representation, for objects with many optional parts
a builder replaces a stack of overloaded constructors whose positional arguments no reader can decode
new objects come from cloning a configured exemplar, which wins when construction is costly or the concrete class is unknown
a clone that copies references shares mutable state with its original, so "a copy" silently is not one
one instance plus a global access point; the global reachability, not the single instance, is the part that causes the damage
a singleton read inside a method body is an undeclared dependency, which is what makes the caller untestable
a named creation method can convey intent and return a cached or subclass instance, neither of which new can do
C. Structural patterns
converts an existing interface into the one a client expects, without modifying either side
offers a simpler entry point over a complex subsystem, which stays fully usable underneath for callers that need it
Adapter converts to an interface you were handed; Facade invents a simpler one you chose
wraps an object in the SAME interface to add behavior, and the wrappers stack at runtime
same interface again, but it controls ACCESS: lazy loading, remoting, caching, permission checks
a decorator adds behavior the client asked for; a proxy decides when and whether the real object is reached at all
treats a leaf and a container uniformly so client code stops branching on which one it is holding
child operations on a leaf must no-op or throw, so the uniformity is bought with a small lie
separates an abstraction hierarchy from its implementation hierarchy so the two vary independently instead of multiplying
Bridge is designed in up front to prevent a class explosion; Adapter is retrofitted to reconcile code you cannot change
shares intrinsic state across many instances and passes extrinsic state in per call, trading object count for parameter noise
D. Behavioral patterns
an interchangeable algorithm selected by the client and injected, replacing a conditional over variants
behavior varies with an internal state object, and the state objects themselves decide the transitions
identical diagrams, different driver: the client picks the strategy, the states pick their successor
the base class fixes the algorithm's skeleton and defers named steps to subclasses
subjects broadcast to registered observers without knowing who or how many they are
an observer that never unregisters keeps itself and the subject alive; the lapsed listener is the classic leak
peers talk to one hub instead of to each other, trading N-to-N coupling for a hub that can grow into a god object
a request captured as an object, which is what makes queueing, logging, retry and undo possible
undo requires the command to store enough prior state to reverse itself, not merely to re-run
a request travels along handlers until one takes it, and nothing guarantees any of them will
traversal is externalized into its own object so the collection's internal structure stays hidden
mutating a collection mid-traversal is exactly the fault fail-fast iterators exist to surface
moves an operation out of a class hierarchy so a NEW operation needs no edit to the element classes
Visitor makes new operations cheap and new element types expensive; ordinary polymorphism trades exactly the other way
captures an object's internal state for later restore without exposing that state to the caller
one class per grammar rule, evaluated over a syntax tree; practical only for small, stable languages
E. Choosing between the near-twins
asking whether the client, the object itself, or a third party selects the next behavior separates Strategy, State and Command
Observer broadcasts a fact and does not care who listens; Mediator coordinates a workflow and knows its participants
Command reifies a request so it can be deferred or replayed; Strategy reifies an algorithm so it can be swapped
both wrap the same interface, but Composite holds many children and Decorator holds exactly one
Builder controls a multi-step construction process; a factory hides which class gets chosen
with few stable transitions a switch is clearer; the State pattern pays for itself only once transitions multiply
choose the pattern that resolves the force you actually have, never the one whose diagram your code already resembles
F. SOLID and its real trade-offs
a module should answer to one actor; the criterion is who requests the change, not "it does one thing"
splitting by verb yields one-method classes and relocates the complexity into wiring
open for extension, closed for modification: add behavior by adding code rather than editing working code
you can only close against variation you anticipated correctly, so closing the wrong axis is speculative generality
a subtype must be usable anywhere its supertype is, with no surprise visible to the caller
a mutable Square breaks Rectangle's independent-sides contract, so the inheritance is invalid even though "a square is a rectangle" is true
an override may weaken preconditions and must strengthen or preserve postconditions and invariants, never the reverse
clients should not depend on methods they never call, or an unrelated change forces them to recompile and re-stub
one interface per method is not the goal; group members by the ROLE a client plays
high-level policy and low-level detail both depend on an abstraction, and the policy is what owns it
if the interface lives in the implementer's module rather than the caller's, nothing was actually inverted
every principle buys flexibility with indirection, so applied to code that never varies it is pure cost
G. Coupling, cohesion and connascence
who depends on you versus who you depend on; the two have very different costs when something changes
reaching into another module's internals, or communicating through shared global state, are the tightest and worst forms
passing data is fine; passing a flag that selects the callee's branch is the callee's design leaking out
two calls that must happen in a fixed order with nothing in either signature saying so
passing a whole record when the callee needs two fields drags the record's shape into the dependency
an interface placed between two things that always change together adds a hop and hides the real connection
how strongly a module's parts serve one purpose; functional cohesion is the target and coincidental grouping the worst case
positional arguments break silently when reordered, and named parameters upgrade the same coupling to a safer form
the same coupling is acceptable inside one function and dangerous across a service boundary
H. Composition, inheritance and delegation
a subclass depends on its parent's internals, so a parent change can break it with no call site touched
a base method that calls another overridable method encodes an unwritten contract a subclass can silently violate
inheritance is a claim of substitutability; if you only wanted reuse, you wanted composition
forwarding to a held collaborator gives the reuse without asserting any type relationship
composing N behaviors at runtime by wrapping avoids the 2^N subclasses that static combination would need
horizontal reuse without one hierarchy, and the ambiguity when two mixins define the same member
past two or three levels, the behavior of one object is scattered across files nobody reads together
inheritance is still right when the subtype genuinely substitutes and the hierarchy is closed
I. Dependency injection and inversion of control
the framework decides when your code runs; dependency injection is one specific kind of IoC, not a synonym for it
dependencies arrive in the constructor, so a half-wired instance cannot exist
permits reconfiguration and circular graphs, at the cost of an object that is briefly invalid
a locator is ASKED for dependencies, so they stay hidden inside the code; injection puts them in the signature
passing collaborators in by hand IS dependency injection; the container is an optional convenience
build the object graph once at the entry point so no business class ever references the container
a container resolves from configuration, so a wiring mistake surfaces at startup or first call rather than at compile time
a constructor taking eight collaborators is reporting that the class has eight reasons to change
declare the interface in the client's package and the implementation elsewhere, so the compile-time arrow points inward
the concrete implementation is chosen by configuration at load time instead of being fixed at compile time
J. Domain logic patterns
one procedure per business transaction: simple, duplicative, and genuinely the right choice while logic is thin
a web of objects each carrying data plus the behavior over it, which pays off as branching multiplies
Transaction Script wins early and Domain Model wins late, and switching mid-project is expensive enough to decide deliberately
one instance handling all rows of a table, sitting between the other two and fitting record-set tooling
a boundary of application operations that defines the transaction and the use-case script over the domain
a thin service layer delegates to the domain; a thick one drains the domain back into procedures
objects holding only getters and setters while all behavior sits in services is a Domain Model in shape only
stored procedures split the logic across two languages, two toolchains and two deployment cycles
K. Data source architectural patterns
one object holding all the SQL for a table, returning record sets rather than domain objects
one instance per row that carries database access and deliberately no domain logic
a domain object that knows how to persist itself, which stays simple only while the object model mirrors the schema
persistence baked into the object means every unit test drags in a database or a heavy fake
a separate layer moves data between objects and the database, leaving both ignorant of each other
Active Record when the schema mirrors the model; Data Mapper when the two must be free to evolve apart
a Gateway exposes the external resource's own shape; a Mapper translates so neither side knows the other exists
persistence annotations on domain classes are a pragmatic compromise, not a clean Data Mapper
L. Object-relational mapping patterns
tracks every object touched during a business transaction and writes the changes in one coordinated commit
the unit of work must order inserts, updates and deletes so foreign keys are never momentarily violated
one row maps to exactly one in-memory object per session, so two reads of the same key return the same instance
a placeholder fetches the data on first access, at the price of a query invisible at the call site
iterating a lazily-loaded collection issues one query per element, and eager fetch or a batch join is the fix
touching a lazy field after the session closes fails, which is why DTOs must be mapped while it is still open
keeping the database key inside the object is what lets the mapper find that row again later
a single-valued reference between two objects becomes a foreign key column on one side
a many-to-many association needs its own table, because neither object's row can hold the other's key
one table for the whole hierarchy: no joins, at the cost of columns that are null for most rows
one table per class joined by key: normalized, at the cost of a join per level of the hierarchy
one table per concrete class: no joins, but a superclass query must union them and keys must be unique across tables
a collection-like interface over the mapping layer, so the domain asks for objects and never mentions rows
a repository returning a live query object or a lazy proxy has not actually hidden persistence from its caller
M. Web presentation patterns
separates presentation from domain AND input handling from output, and the second split is the half people forget
one controller per page or action: easy to follow, and it duplicates whatever every page shares
one handler receives every request and dispatches, giving a single place for auth, logging and routing
markup with markers for dynamic content: readable, and it invites logic to creep into the page
transforms domain data element by element into output, which makes rendering unit-testable
build a logical page first and render it second, so a site-wide look change is one edit instead of hundreds
screen flow and navigation live in their own object, which is what wizards and state-dependent screens need
a query issued from a template breaks the layering and hides its cost inside rendering
N. Distribution and session state
do not distribute your objects: every remote hop adds latency and an entirely new failure mode
a coarse-grained facade over fine-grained objects so one call replaces a chatty sequence of them
a serializable carrier that batches data for a single call and holds no behavior
sending domain objects over the wire couples every client to your internal model and its version history
the translation between DTO and domain object belongs in its own object, not inside either of them
state travels with the client: scales freely, but it is untrusted and must be signed or validated
state kept in server memory: simplest to write, and it forces session affinity or replication
session state as rows: survives failover, at a round trip per request
O. Offline concurrency
an edit spanning several requests cannot be held open in one database transaction, which is why these patterns exist at all
detect the conflict at commit by comparing a version, and make the loser redo the work
prevent the conflict by taking a lock up front, and accept the deadlock, timeout and liveness burden
choose by conflict probability and by how expensive the lost work would be, not by taste
lock a whole aggregate with one lock so its parts cannot be edited into a mutually inconsistent state
acquire the lock inside the framework, because one developer forgetting the explicit call defeats the entire scheme
two read-modify-write cycles where the second silently overwrites the first
data read at different moments within one business transaction may not describe any single consistent state
P. Base patterns
wrap any external system behind a simple interface expressed in YOUR vocabulary, not the vendor's
a stand-in for a slow or unreliable dependency so tests can run without it
one superclass per layer holding what every type in that layer shares
a well-known object others look things up in, which is a global by another name, so its scope must be chosen deliberately
equality by value rather than identity: two instances with the same fields are interchangeable
a mutable value shared by two owners changes under both, which is the whole reason value objects are frozen
the currency belongs with the amount, and allocation must divide without losing or inventing cents
a subclass that encodes one particular case, Null Object being the best-known, replacing checks scattered across callers
Q. Code smells
a smell says "look here", not "fix this"; whether it actually hurts is a judgment call
length alone is not the fault; mixing levels of abstraction inside one function is
many parameters usually mean a missing type or a dependency that should have been injected
a boolean that selects a branch means the function is really two functions sharing a name
the cost is not the extra lines, it is the edit you make in three places and forget in the fourth
one module edited for many unrelated reasons: it holds more than one responsibility
one change forcing edits across many modules: one responsibility smeared across all of them
a method more interested in another object's data than its own belongs with that data
the same group of fields travelling together everywhere is a type asking to be born
a string for an email or an int for money discards every invariant a type could have enforced
abstraction added for a requirement that never arrived, now taxing every reader
a subclass inheriting members it does not want is telling you it wanted delegation
a comment explaining WHAT a block does usually marks a function waiting to be extracted and named
one class that knows and does everything, so every change routes through it and no change stays local
R. Refactoring: the discipline and the core moves
refactoring changes structure without changing observable behavior; doing both at once is not refactoring
add a feature or refactor, never in the same edit, so a red test has exactly one candidate cause
without a suite you can run in seconds you are editing hopefully, not refactoring
the point of a catalog step is that it is small enough to be obviously safe and trivially reversible
extract when the fragment needs a comment to explain it, because the name then replaces the comment
when the body says as much as the name, the indirection is noise and should go
naming a subexpression documents a calculation without moving any code
reshaping a signature safely means add-new, forward-the-old, migrate callers, then remove
a function belongs in the module that owns the data it uses most
split when a subset of the fields and the methods that touch them only talk to each other
a class that no longer earns its keep should be folded back in before people start working around it
encapsulating a chain and later undoing it are opposite moves, each correct at a different time
replace a data clump with a type that can then grow behavior of its own
S. Refactoring conditionals, data and hierarchies
extract the test and each branch into named functions so the intent survives the next reader
several tests with the same outcome are really one test that deserves one name
return early on the unusual cases so the main path stays unindented
when the same switch over a type repeats, the branches want to become subclasses or strategies
a single switch in a single place is clearer than a hierarchy spread across five files
a repeated null-or-missing check becomes an object that answers for itself
errors the caller should not routinely handle belong in the exception channel, not the return value
wrap access first when you are about to change a widely-referenced piece of data
a temp caching a calculation blocks extraction, and a query does not
the moment a primitive needs validation or formatting, it wants to be a class
returning a live collection lets callers mutate your state behind your back; return a copy or expose add/remove
separate the code that parses or validates input from the code that computes with it
a named map/filter/reduce chain states what the loop was accumulating
inheritance can be spent once, while delegation can be changed at runtime and combined
T. Anti-patterns
a commonly-reached-for solution that is reliably worse than an available alternative AND has a known refactoring out
the tool you know best applied to the problem it does not fit
no discernible architecture, every module reaching into every other, and it usually got there by winning on schedule
dead code nobody dares delete because nobody can prove what still depends on it
a configuration system so general it reimplements the programming language, badly
optimizing before measuring buys complexity against a cost you never confirmed exists
an unexplained literal is a name somebody declined to write
an empty catch block converts a loud fault into a silent wrong answer
a wrapper that exposes the vendor's own types relocates the coupling instead of removing it
U. Domain-driven design and architecture-level patterns
one vocabulary shared by the code and the domain experts; a translation step between them is where the bugs breed
an entity is identified by a continuous identity, so two entities with identical fields are still different things
an aggregate is a consistency boundary that outside code may only reach through its root
one transaction should modify one aggregate, and other aggregates catch up through events
the same word means different things to different teams, and forcing one shared model across them is the classic failure
a deliberate translation layer keeps another system's model from leaking into yours
a named fact that already happened, expressed in the domain's language, and the thing other contexts react to
the domain declares ports and adapters for UI, database and tests plug into them from outside
source dependencies point inward toward policy, which is the single rule that layered, onion, clean and hexagonal all draw differently
separate the write model from the read model when their shapes genuinely differ, and accept eventual consistency between them
store the sequence of state changes as the record of truth and derive current state by replaying it
replay time, versioning of old events and the loss of simple ad-hoc queries are the price of the perfect audit log
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Coding Patterns practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC