Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

Structural & Behavioral Patterns

9 min read

In a Nutshell

Beyond creating objects, design patterns address two other recurring concerns. Structural patterns deal with how objects and classes are composed into larger structures — how to combine pieces so the whole is flexible and the relationships are clean (Adapter, Decorator, Facade, Proxy, Composite). Behavioral patterns deal with how objects communicate and share responsibility — how they interact, distribute work, and respond to events (Observer, Strategy, Command, Iterator, State). Where creational patterns answer "how do I make objects?", structural patterns answer "how do I assemble them?" and behavioral patterns answer "how do they collaborate?" These patterns are the vocabulary of good object-oriented design and appear constantly in real systems.

2D minimalistic diagram split in two: left labeled "Structural" shows objects being composed/wrapped into larger structures (a decorator wrapping an object, an adapter bridging two incompatible interfaces, a facade fronting a subsystem); right labeled "Behavioral" shows objects communicating (an observer notifying subscribers, a strategy being swapped in), illustrating composition vs collaboration

How It Actually Works

Structural Patterns: Composing Objects

Pattern Problem It Solves One-Liner
Adapter Two incompatible interfaces need to work together Wrap one interface to look like another
Decorator Add behavior without subclassing Wrap an object to extend it dynamically
Facade A complex subsystem is hard to use A simple front door to a complex system
Proxy Control access to an object A stand-in that adds control (lazy load, auth, cache)
Composite Treat individual and grouped objects uniformly A tree where leaves and branches share an interface

Adapter bridges incompatible interfaces (wrap a third-party library to match your interface). Decorator adds behavior by wrapping (add logging/caching/compression around a component without changing it). Facade hides complexity behind a simple interface (a single OrderService.placeOrder() fronting inventory, payment, and shipping subsystems). Proxy stands in for an object to add control (lazy loading, access control, caching — the caching and service mesh sidecar are proxy-pattern applications).

Behavioral Patterns: Coordinating Objects

Pattern Problem It Solves One-Liner
Observer Many objects need to react to a state change Publish state changes to subscribers
Strategy Swap algorithms at runtime Encapsulate interchangeable algorithms
Command Encapsulate a request as an object Requests become objects (queue, undo, log them)
Iterator Traverse a collection without exposing internals Sequential access without knowing the structure
State Behavior changes with internal state An object behaves differently per state

Observer is the OOP root of pub/sub — subjects notify subscribers of changes (event listeners, reactive UIs). Strategy encapsulates interchangeable algorithms behind a common interface so you can swap them at runtime (different pricing, sorting, or routing strategies chosen by config). Command turns a request into an object you can queue, log, or undo (the basis of task queues and undo systems). State lets an object change behavior as its state changes (the circuit breaker is a state machine).

Strategy: The Workhorse Behavioral Pattern

Strategy is worth a closer look because it's everywhere and embodies "composition over inheritance":

# Instead of subclasses or if/else chains for each algorithm:
class ShippingCalculator:
    def __init__(self, strategy):
        self.strategy = strategy         # inject the algorithm
    def cost(self, order):
        return self.strategy.calculate(order)

# Interchangeable strategies behind one interface:
calc = ShippingCalculator(ExpressShipping())   # swap at runtime
calc = ShippingCalculator(StandardShipping())  # or by config

This replaces sprawling conditionals with pluggable, testable, independently-evolving algorithms — the same idea behind swappable load-balancing algorithms and eviction policies.

Patterns Are a Vocabulary, Not a Checklist

The real value of patterns is shared vocabulary and proven structure — saying "wrap it in a decorator" or "use a strategy here" communicates a whole design instantly. But patterns can be over-applied: forcing patterns where a simple function suffices adds needless indirection. Use them when they solve a real problem (flexibility, decoupling, clarity), not to demonstrate cleverness.

2D minimalistic diagram showing the Decorator pattern: a core "DataSource" object being wrapped by successive decorators — a "Compression" wrapper around it, then an "Encryption" wrapper around that, then a "Caching" wrapper — each adding behavior while preserving the same interface, so the stack can be composed in any combination without modifying the core object

Seeing It in Action

Scenario: Applying structural and behavioral patterns in a data-processing service.

# STRUCTURAL — Decorator: compose cross-cutting behavior around a data source
#   Each wrapper adds one concern; the interface stays identical.
source = FileDataSource("data.bin")
source = CompressionDecorator(source)   # transparently compress/decompress
source = EncryptionDecorator(source)    # transparently encrypt/decrypt
source = CachingDecorator(source)       # transparently cache reads
source.read()   # flows through cache → decrypt → decompress → file
#   Add/remove/reorder concerns by changing the wrapping — no core edits.

# STRUCTURAL — Facade: hide a complex subsystem behind one simple call
class OrderFacade:
    def place_order(self, cart):
        self.inventory.reserve(cart)     # three subsystems coordinated
        self.payment.charge(cart)         # behind ONE simple method the
        self.shipping.schedule(cart)      # caller can use without knowing them
#   Callers do OrderFacade().place_order(cart) — complexity hidden.

# BEHAVIORAL — Strategy: swap the processing algorithm at runtime/config
processor = DataProcessor(strategy=BatchStrategy())    # or StreamStrategy()
processor.run(dataset)   # algorithm is pluggable and independently testable

# BEHAVIORAL — Observer: notify many subscribers of an event (pub/sub roots)
pipeline.on_complete.subscribe(send_notification)   # each subscriber reacts
pipeline.on_complete.subscribe(update_dashboard)    # independently to the
pipeline.on_complete.subscribe(trigger_next_job)    # same "job done" event

# Why each pattern earns its place:
#  Decorator: layer compression/encryption/caching in any combination without
#             touching the core source or writing 2^N subclasses.
#  Facade:    give callers a simple entry point to a genuinely complex flow.
#  Strategy:  switch batch vs stream processing by config; test each alone.
#  Observer:  decouple the event producer from an open set of reactors —
#             the OOP foundation of the pub/sub you'd use at system scale.

Why structural and behavioral patterns matter: these patterns are the concrete, object-level expression of the same principles that govern good system design at scale — and recognizing that connection is what makes them valuable rather than academic. Structural patterns are about composition: the Decorator lets you layer independent concerns (compression, encryption, caching) around a component in any combination without an explosion of subclasses or edits to the core — the same "wrap to add behavior transparently" idea behind a caching proxy or a service-mesh sidecar. The Facade tames complexity by giving callers one clean entry point to a subsystem, exactly as an API gateway fronts many services. Behavioral patterns are about collaboration: the Strategy pattern replaces sprawling conditionals with pluggable, independently-testable algorithms (batch vs stream, this pricing rule vs that), the same shape as swappable load-balancing or eviction policies; and the Observer pattern — subjects notifying an open set of subscribers — is literally the object-level root of pub/sub messaging. The deeper lesson is that these patterns give you a shared vocabulary and proven structures for the two questions that dominate design after "how do I create objects?": how do I compose pieces cleanly, and how do they communicate without tight coupling? Used deliberately — where they solve a real problem of flexibility, decoupling, or clarity, and not forced in where a plain function would do — they make code more adaptable to the change that always comes.

Interview Questions

  1. Q: What's the difference between structural and behavioral patterns? Hint: Structural patterns deal with how objects/classes are composed into larger structures — clean, flexible relationships (Adapter, Decorator, Facade, Proxy, Composite). Behavioral patterns deal with how objects communicate and distribute responsibility — interaction and collaboration (Observer, Strategy, Command, Iterator, State). Structural = "how do I assemble objects?"; behavioral = "how do they collaborate?" (Creational = "how do I create them?")

  2. Q: Explain the Decorator pattern and its advantage over subclassing. Hint: Decorator wraps an object to add behavior dynamically while preserving the same interface, so wrappers can be stacked in any combination (compression + encryption + caching around a data source). Advantage over subclassing: you avoid a combinatorial explosion of subclasses for every combination of behaviors, add/remove concerns at runtime, and follow composition-over-inheritance — behavior is layered, not baked into a class hierarchy.

  3. Q: What is the Strategy pattern and why is it useful? Hint: It encapsulates interchangeable algorithms behind a common interface and lets you select/swap them at runtime or by config (different shipping, pricing, sorting, routing algorithms). It replaces sprawling if/else conditionals with pluggable, independently-testable, independently-evolving algorithm objects — embodying composition over inheritance and the open/closed principle. The same idea underlies swappable load-balancing and cache-eviction policies.

  4. Q: How does the Observer pattern relate to pub/sub messaging? Hint: Observer is the object-level root of pub/sub: a subject maintains a list of subscribers and notifies them of state changes, decoupling the producer from an open set of reactors. Pub/sub scales this idea across processes/services with a broker, but the core concept — publish an event, all interested subscribers react independently, publisher unaware of them — is identical. Event listeners and reactive UIs are Observer in the small.

  5. Q: What's the risk of overusing design patterns, and how do you decide when to apply one? Hint: Forcing patterns where a simple function or class suffices adds needless indirection, abstraction, and cognitive load — complexity that doesn't pay for itself. Patterns are a vocabulary and proven structures, not a checklist to maximize. Apply one when it solves a real problem (flexibility to swap implementations, decoupling, taming genuine complexity, clarity), not to demonstrate cleverness. Prefer the simplest thing that works.

References

Dive Deeper