skip to content

Structural Styles

How a system is partitioned and deployed: monolith, modular monolith, layered, client-server, peer-to-peer, space-based, microkernel and dataflow pipelines. These are the shapes that determine what a deployment unit is before any distribution question comes up.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

In a microkernel (plug-in) architecture, what is the difference between the 'core system' and a 'plug-in', and what does each side own?

level: juniorimportance: must knowfreq 55%

answer

  1. core = minimal engine + registry + contract
  2. plug-ins depend on core, not vice versa (DIP)
  3. IDE/rule-engine/browser examples
  4. contract change breaks all plug-ins

basics

~10 s

The core is a small always-on engine that only knows how to load and talk to plug-ins. Plug-ins are separate, swappable pieces of code that add actual features, each following rules the core sets.

solid answer

~40 s

The core system contains the minimum machinery needed to run: process/lifecycle management, a plug-in registry, and a communication mechanism (interfaces, a service locator, or a message bus). It has no knowledge of concrete business features. A plug-in is a self-contained module that implements a contract the core publishes — an interface, an extension point, or a manifest describing what it provides and requires. The core discovers plug-ins (via config, classpath scanning, or explicit registration), instantiates them, and routes calls through the contract rather than concrete classes. This inversion means the core never imports plug-in code, only plug-ins depend on the core's API. Adding, removing, or upgrading a feature means adding/removing/upgrading a plug-in without touching or redeploying the core.

go deeper

for a junior

Should describe the core/plug-in split in plain terms and give one example (e.g. IDE with language plug-ins) without needing to name specific technologies.

for a middle

Should explain that plug-ins implement a contract the core defines, and that dependency flows from plug-in to core, not the reverse.

for a senior

Should describe concretely how discovery/registration works (manifest, classpath scan, service loader) and discuss contract stability as an ongoing design concern.

for a principal

Should discuss contract governance across a plug-in ecosystem over years — versioned extension points, backward compatibility strategy, and how to prevent the core from re-accumulating coupling as it grows.

## The two halves The microkernel pattern (also called plug-in architecture) splits a system into two categories of component with sharply different responsibilities: a **core system** and a set of **plug-ins**. The core system is deliberately kept small — it implements only the minimal functionality required to run the system: starting up, loading configuration, managing the lifecycle of components, and providing a mechanism through which plug-ins can be discovered and invoked. ## What the core owns Concretely, the core typically owns three things: - **(1) a contract** — one or more interfaces, abstract classes, or schema definitions that describe what a plug-in must expose; - **(2) a registry** — a data structure (in-memory map, service registry, OSGi service registry, or a config file listing implementation classes) that tracks which plug-ins are installed and their metadata (id, version, dependencies); and - **(3) a dispatch mechanism** — code in the core that, when a capability is needed, looks the capability up in the registry and invokes it through the contract rather than calling a concrete class directly. ## What a plug-in owns A plug-in, by contrast, is a self-contained unit of feature logic. It implements the contract the core defines, and it typically ships as an independently deployable artifact — a JAR, a DLL, a bundle, an npm package — with its own manifest describing its identity, version, and the extension points it fills or the services it requires from other plug-ins. Crucially, **dependency direction only flows one way**: plug-ins depend on the core's published API, but the core has zero compile-time knowledge of any specific plug-in. This is the same idea as the **Dependency Inversion Principle** applied at the architecture level — the core defines an abstraction, and concrete implementations (plug-ins) are injected or discovered at runtime rather than referenced by name in the core's source. ## Why the split exists Why does this split exist? The motivating problem is a product with a stable, universally-needed nucleus but a long, variable, and unpredictable list of optional features — the classic examples are: - **IDEs** (a text-editing and project-management core plus language/tooling plug-ins), - **rule engines** (a rule-evaluation core plus domain-specific rule packs), and - **browsers** (a rendering/networking core plus extensions). If every feature were compiled directly into one monolith, every new feature would force a full rebuild and redeploy of the whole application, feature teams would step on each other's code, and users who don't need feature X would still pay its footprint and its bug surface. By moving optional capability behind a plug-in boundary, the core can ship once and stay largely frozen, while plug-ins are built, tested, versioned, and released independently — sometimes even by third parties who never see the core's source code at all (this is how Eclipse and IntelliJ extension ecosystems work, and how enterprise software vendors let customers write custom plug-ins without touching vendor code). ## How it looks in practice The concrete mechanics of 'what the core owns vs. what a plug-in owns' usually look like this in a Java-ish stack: the core defines an interface such as `ExportFormat { fun export(doc: Document): ByteArray }` and a registry `ExportFormatRegistry`. At startup the core scans a plug-ins directory (or reads a manifest, or queries a DI container) for classes implementing `ExportFormat`, instantiates each one, and registers it under an id. When the user asks to export as PDF, the core looks up "pdf" in the registry and calls `.export()` on whatever was registered — it never has a `PdfExportFormat` symbol in its own compiled code. The PDF plug-in, in turn, owns all PDF-specific logic, its own dependencies (e.g. a PDF library), and its own versioning; it can be upgraded or removed without recompiling the core. ## The cost of the split The main cost of this split is indirection and discipline: - **The core's contract has to be designed up front and evolved carefully**, because every plug-in depends on it — a breaking interface change breaks every plug-in simultaneously, which is a much larger blast radius than changing an internal class in a monolith. - **There's also a real risk of the core creeping**: if new plug-in requirements keep forcing new methods onto the core's contract, the core stops being 'minimal' and starts re-accumulating the coupling the pattern was meant to remove. A well-run microkernel system therefore needs governance over the contract itself, often versioned extension points (v1/v2 APIs) so old plug-ins keep working while new capability is added — exactly how the Eclipse and IntelliJ platform APIs evolve over years without breaking the plug-in ecosystem outright.

  • How does the core actually find plug-ins at runtime without knowing their class names ahead of time?
    Typically via a discovery mechanism: scanning a designated directory or classpath for artifacts that declare a specific manifest entry or implement a marker interface, reading a config file that lists implementation class names, or using a service-loading facility like Java's ServiceLoader or an OSGi service registry. The core then reflectively instantiates whatever it finds and registers it against the contract's interface, so no plug-in class name ever appears in the core's own source.
  • What happens if two plug-ins need to communicate with each other, not just with the core?
    The core usually mediates this too, exposing a shared event bus, service registry, or extension-point mechanism so plug-ins can publish capabilities other plug-ins consume, rather than plug-ins referencing each other's classes directly. Direct plug-in-to-plug-in dependencies reintroduce tight coupling and defeat the purpose of the pattern, so mature platforms treat inter-plug-in dependencies as first-class, versioned relationships tracked by the core.

Like a power strip (the core) that only knows the shape of a plug socket — it has no idea whether you plug in a lamp, a phone charger, or a blender; each device (plug-in) brings its own function as long as its plug matches the socket shape (the contract).

saying these in an interview costs you the question

  • Says the core imports and calls concrete plug-in classes directly
  • Can't explain why the core has no compile-time reference to plug-ins
  • Thinks 'plug-in' just means 'separate JAR' with no contract behind it
  • Doesn't mention discovery/registry as how plug-ins get found

context

open as a page

In plain terms, what is the Pipes and Filters architectural style, and can you give an everyday example of it?

level: juniorimportance: must knowfreq 70%

basics

~20 s

It's a design where data flows through a chain of independent processing steps called filters, connected by pipes. Each filter does one small job and hands its output to the next, like workers on an assembly line.

open as a page

What is the client-server architectural style, and what does it mean for a client to send a 'request' and a server to send back a 'response'?

level: juniorimportance: must knowfreq 85%

basics

~20 s

The client-server style splits an app into two roles: a client that asks for something (a request), and a server that has the data or logic and answers back (a response). The client initiates; the server waits and replies.

open as a page

In a typical N-tier layered architecture with presentation, business, and data access layers, which direction are calls allowed to flow, and why shouldn't the data access layer call back into the presentation layer?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Calls only go downward: UI talks to business logic, which talks to data access. Data access never calls back up - that would tangle everything together, making each part impossible to change or test alone.

open as a page

What is a modular monolith, and what concretely distinguishes it from a monolith whose packages happen to be organized by feature but have no enforced boundaries?

level: juniorimportance: must knowfreq 80%

basics

~10 s

One app, one deployment, but cut into modules that hide their internals and only talk through defined entry points - unlike a folder-organized monolith where any code can still call any other code directly.

open as a page

In a monolithic application, all the business logic, UI, and data-access code are compiled and deployed together as one running program. What does 'shared process and memory' mean for how components inside that application call each other, compared to components that live in separate services?

level: juniorimportance: must knowfreq 85%

basics

~10 s

Inside a monolith, all the code runs in one program, so different parts call each other directly through normal function calls, instantly and without any network involved.

open as a page

In a peer-to-peer (P2P) network architecture, what does it mean for a node to act as both a client and a server at the same time?

level: juniorimportance: must knowfreq 80%

basics

~20 s

Every computer in the network can both ask for things and give things to other computers -- there's no single 'boss' machine. Each one shares data directly with others, like friends swapping items instead of going through a store.

open as a page

In plain terms, what problem does Space-Based Architecture (SBA) solve, and what does it remove from the synchronous request path?

level: juniorimportance: must knowfreq 55%

basics

~20 s

SBA stores live data in memory across many machines instead of a single database, so a request never has to wait on one central database. That removes the database as the bottleneck when traffic suddenly spikes.

open as a page

When designing the contract between a microkernel core and its plug-ins (the interface plug-ins must implement), what belongs in that contract and what design choices affect how safely plug-ins can be added later?

level: middleimportance: must knowfreq 65%

basics

~20 s

The contract is the fixed set of methods and data a plug-in must provide so the core can call it generically. Keep it small, stable, and versioned so new plug-ins can be added without changing old ones or the core.

open as a page

What makes filters in a Pipes and Filters pipeline composable — able to be freely reordered, swapped, or reused across different pipelines — and what design discipline is required to keep that property?

level: middleimportance: must knowfreq 60%

basics

~20 s

Filters are composable because each one only cares about the data format coming in and going out, not about which specific filter is next to it. Keeping filters stateless and format-agnostic is what preserves that swappability.

open as a page

In a Pipes and Filters pipeline, what's the difference between a push-based and a pull-based data flow model, and what trade-off does each make?

level: middleimportance: must knowfreq 55%

basics

~20 s

Push means the upstream filter sends data downstream as soon as it's ready, like shoving items down a conveyor belt. Pull means the downstream filter asks for the next item when it's ready to handle it, like taking items off a shelf one at a time.

open as a page

What's the difference between a stateless server and a stateful server in a client-server system, and why does it matter for scaling and failover?

level: middleimportance: must knowfreq 80%

basics

~20 s

A stateless server treats every request independently and remembers nothing about the client between requests; the client must resend any context each time. A stateful server keeps track of a client's session (like being logged in) across multiple requests.

open as a page

Concretely, what does it mean for a layer to 'leak' its concerns into another layer in a layered architecture, and how does repeated leakage turn a codebase into a 'big ball of mud'?

level: middleimportance: must knowfreq 70%

basics

~20 s

Leakage is when a layer's private details (like SQL, or UI formatting) show up inside another layer. Repeat that across a codebase and layers stop meaning anything - everything depends on everything, and nothing can change alone.

open as a page

What's the difference between 'strict' layering (each layer may only call the layer immediately below it) and 'relaxed' or 'open' layering (a layer may call any layer below it, not just the adjacent one), and what does each cost you?

level: middleimportance: must knowfreq 65%

basics

~20 s

Strict layering: each layer can only call the layer directly beneath it. Relaxed/open layering: a layer may call any lower layer, skipping ones in between. Strict is safer but more boilerplate; relaxed is faster but easier to tangle.

open as a page

In a Spring Modulith-based application, how should the `billing` module get data it needs from the `orders` module, and why is calling `orders`'s JPA repository directly still a boundary violation even though it would compile fine?

level: middleimportance: must knowfreq 68%

basics

~20 s

Billing should call a small public interface (or listen to an event) that orders exposes on purpose - not reach into orders' database code - because reaching into internals means any change inside orders can silently break billing.

open as a page

A team packages their entire application — web UI, order processing, and inventory logic — into one JAR file that gets built by one CI pipeline and deployed to production as a single unit. What operational advantage does this single-artifact deployment give the team day to day, and what's the corresponding cost when only the inventory logic needs to change?

level: middleimportance: must knowfreq 80%

basics

~20 s

One build, one thing to deploy — simple and predictable. But if you only changed one small piece, you still have to rebuild, retest, and redeploy the whole app, and unrelated failures can block your release.

open as a page

In a peer-to-peer system, how does a gossip (epidemic) protocol propagate information like membership or liveness updates, and what does it trade off against a structured DHT lookup for that job?

level: middleimportance: must knowfreq 60%

basics

~20 s

Gossip is how peers spread news the way rumors spread in a crowd: each peer periodically tells a few random other peers what it knows, and they tell a few more, until eventually almost everyone has heard it -- no one broadcasts to everyone at once.

open as a page

What is an 'overlay network' in a peer-to-peer system, and what's the practical difference between a structured overlay and an unstructured overlay when a peer needs to find a resource?

level: middleimportance: must knowfreq 70%

basics

~20 s

An overlay network is a map of which peers talk to which, layered on top of the real internet. 'Structured' means peers are organized in a strict pattern so you can find things quickly and predictably; 'unstructured' means connections are more random, so finding things means asking around and hoping.

open as a page

In an in-memory data grid used for Space-Based Architecture, what's the practical trade-off between a fully replicated cache topology and a partitioned (sharded) cache topology?

level: middleimportance: must knowfreq 55%

basics

~20 s

Replicated means every node has a full copy of all the data — fast reads everywhere but limited by how much fits on one machine and slower writes. Partitioned means each node holds only a slice — you can hold way more data total, but reads/writes for data you don't hold need a network hop.

open as a page

What does a 'processing unit' (PU) bundle together in Space-Based Architecture, and why is that bundling important for how the system scales?

level: middleimportance: must knowfreq 60%

basics

~20 s

A processing unit is a self-contained package of business logic plus its own slice of in-memory data plus the engine to run it — like a mini self-sufficient server. Bundling them means you scale by cloning whole units, not by separately scaling app servers and a database.

open as a page

What concrete benefits does isolating features as separate plug-ins give you, and what concrete costs — performance, complexity, dependency management — does that isolation impose in return?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Splitting features into plug-ins means one broken or slow feature is less likely to crash everything else, and features can be updated separately. The cost is extra complexity: indirect calls are slower and different plug-ins can need conflicting versions of the same library.

open as a page

When a fast producer filter feeds a slower consumer filter through a bounded pipe, what concrete backpressure strategies exist, and what does each one cost?

level: seniorimportance: must knowfreq 65%

basics

~20 s

When the pipe fills up because the consumer can't keep up, you either make the producer wait (slows the whole pipeline), throw away some data (loses it), or let the buffer grow (risks running out of memory). Each choice trades speed, correctness, or memory.

open as a page

A two-tier client-server app (a client with embedded database queries talking directly to a database) is evolving into a three-tier architecture. What gets added in the middle, and why?

level: seniorimportance: must knowfreq 70%

basics

~20 s

Multi-tier splits the server side further: instead of a client talking straight to a database, you add a middle layer (an application server) between them, so presentation, business logic, and data each live in their own layer.

open as a page

A team building a strictly layered application (presentation -> business -> data access, where each layer may call only the layer below it) keeps merging pull requests that quietly violate the rule - e.g., a controller class importing a repository class directly. Code review alone isn't catching these. What mechanisms can enforce a layered architecture's dependency-direction rule automatically, and what exactly do they check?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Automated build-time checks - tools that scan the code's package/module structure and fail the build if, say, a UI package imports a database package. This catches violations every time, unlike relying on humans remembering the rule during review.

open as a page

A modular monolith has clean, enforced module boundaries. What specifically makes extracting one of its modules into a standalone microservice straightforward, and what commonly makes it hard in practice even when the code-level boundary looks perfect?

level: seniorimportance: must knowfreq 62%

basics

~20 s

If a module only talks to others through its public API/events and doesn't share its database tables, you can swap that in-process API for a network call with far less risk - but if it secretly shares tables or needs instant answers from another module, extraction breaks things until you fix that first.

open as a page

In a monolithic codebase without enforced module boundaries, a change to a shared data-model class used by both the billing code path and the reporting code path breaks the reporting module's tests. Why does this kind of ripple effect happen more easily in a monolith than it would across independently deployed services, and what does it cost the team over time?

level: seniorimportance: must knowfreq 75%

basics

~20 s

Because both billing and reporting directly use the same shared class in the same codebase, a change one team makes can silently break the other team's code, unlike separate services which only talk through a stable, agreed-upon interface.

open as a page

In a distributed hash table (DHT) like Chord or Kademlia, how does a peer route a lookup for a given key to the peer responsible for it, and why does this typically take O(log n) hops for a network of n peers?

level: seniorimportance: must knowfreq 65%

basics

~20 s

Every peer and every piece of data gets a number. Each peer only directly knows a few other peers, chosen so at each step you jump about halfway closer to the number you're looking for -- like narrowing down a phonebook by splitting it in half each time, so it only takes a handful of hops even in a huge network.

open as a page

When an in-memory data grid uses asynchronous 'write-behind' to persist data to a backing relational database, what durability risk does this introduce, and how do production systems mitigate it?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Write-behind means the system says 'saved' as soon as data hits memory, then writes to the real database later in the background. If the machine crashes before that background write happens, that data can be lost — so systems copy it to a backup machine first.

open as a page

Walk through what actually happens, step by step, from a plug-in artifact sitting in a directory to it being registered, activated, and callable by the core — and what has to happen for it to be safely unloaded.

level: middleimportance: should knowfreq 50%

basics

~20 s

The core scans for plug-ins, reads their info, loads their code, calls an init/start hook, and adds them to a lookup table the core uses to find them by name. Unloading reverses this: stop the plug-in, free its resources, remove it from the table.

open as a page

In a client-server design, what's the difference between a thin client and a fat (thick) client, and what trade-offs push a team toward one or the other?

level: middleimportance: should knowfreq 55%

basics

~20 s

A thin client does almost no work itself — it just displays what the server sends and forwards user input back. A fat (thick) client does a lot locally — business logic, validation, rendering — and only talks to the server for shared data or authoritative operations.

open as a page

showing 1–30 of 46