If you have ever been in a meeting where someone goes to the whiteboard, draws a box labelled “the system,” then a smaller box inside it labelled “API,” then ten more boxes around that one, and an hour later nobody can agree on what they were supposed to be discussing, you have experienced the central problem of architecture diagrams. The diagrams aren’t actually the problem. The lack of a shared convention for what zoom level you’re at is the problem.
Are we talking about the system as one thing in a wider ecosystem? Or about the deployable units inside the system? Or about the modules inside one of those units? Or about the code? Each of those is a useful conversation. Mixing them in the same diagram is how you get a whiteboard with eighty boxes that nobody can read.
Simon Brown’s C4 model fixes this. It is, depending on how you count, either an extremely modest contribution to software engineering or an underrated revolution. It’s a convention. Four standard zoom levels for talking about a system. Each level has a clear scope, a clear set of legitimate elements, and a clear question it answers. When the team agrees to use the convention, conversations get faster, diagrams get clearer, and the whiteboard stops looking like a Jackson Pollock.
This lesson walks through the four levels, with a worked example for a small SaaS product, and a few opinions on when not to use C4.
The four levels
C4 stands for Context, Container, Component, Code. Each is a zoom level. You move from broadest (Context) to most detailed (Code), and at each level you only show elements appropriate to that zoom.
Level 1: System Context. The system you’re building, sitting inside the wider world. Shows the system as a single box, the human users (the “actors”), and the external systems your system depends on or integrates with. The audience is wide: anyone in the business, technical or not, should be able to read this diagram. The question it answers is “what is this thing, and what does it talk to?”
Level 2: Container. Zooms into the system from level 1. Shows the deployable or runnable units inside it: the web frontend, the API service, the database, the message broker, the worker process, the cache. “Container” here is a Brown-ism that predates Docker by years and refers to “any thing that hosts code or data,” not specifically a Linux container, although in 2026 most containers in this sense run inside actual containers in the Docker sense. The audience is technical: developers, SREs, architects. The question it answers is “what runs where, and how do those things talk to each other?”
Level 3: Component. Zooms into one container from level 2. Shows the major modules, services, or logical groupings inside that container. For an API service, components might be auth, orders, products, billing. For a frontend, components might be the routing layer, the auth context, the data fetching layer, the design system. The audience is the developers working on that container. The question it answers is “how is this one container organised internally?”
Level 4: Code. Zooms into one component from level 3 and shows actual classes, interfaces, or functions. UML class diagrams, basically, scoped to a single component. The audience is whoever is implementing the component. The question it answers is “what are the actual code objects involved?”
The trick of C4 is that you draw the levels you need and skip the ones you don’t. Most useful conversations happen at levels 1, 2, and 3. Level 4 is rarely worth drawing because by the time you’re at the class level, it’s almost always faster to read the code itself than maintain a diagram that goes stale the moment someone refactors. Brown himself says level 4 is optional and most teams skip it.
The other useful aspect: each level lets you have a conversation at that level without dragging the other levels in. When the product owner asks “does the system integrate with Stripe?” you point at the Context diagram. You do not also explain that Stripe is called from the BillingService Spring bean inside the payments package of the api container. They don’t care. They want to know if Stripe is in the picture. The Context diagram says yes.
Worked example: a small SaaS product
Let’s draw all three of the useful levels for a hypothetical small SaaS product. Call it ChartSmith, an online tool for drawing charts. Users sign up, log in, build charts, save them, and export them. Charts are stored in a database, exported via a background worker, billed monthly through Stripe, and confirmation emails go out via SendGrid. There’s a React frontend, a Node API, a Postgres database, a Redis cache, and a Python export worker behind a job queue.
That paragraph is the system in plain English. Now let’s see what each C4 level looks like.
Level 1: System Context
The Context diagram shows ChartSmith as a single box, the people who use it, and the external systems it integrates with.
flowchart LR
user(["ChartSmith user"])
admin(["ChartSmith admin"])
chartsmith["<b>ChartSmith</b><br/><i>Web app for building<br/>and exporting charts</i>"]
stripe["<b>Stripe</b><br/><i>Subscription payments</i>"]
sendgrid["<b>SendGrid</b><br/><i>Transactional email</i>"]
s3["<b>AWS S3</b><br/><i>Export storage</i>"]
user --> chartsmith
admin --> chartsmith
chartsmith --> stripe
chartsmith --> sendgrid
chartsmith --> s3
classDef person fill:#e6f4f1,stroke:#0d9488,color:#0c1419
classDef sys fill:#0d9488,stroke:#0d9488,color:#ffffff
classDef ext fill:#fff5e6,stroke:#c89200,color:#5a3e00
class user,admin person
class chartsmith sys
class stripe,sendgrid,s3 ext
Notice what’s not on this diagram. There’s no mention of the database, or the cache, or whether the API is REST or GraphQL, or whether the frontend is React. Those details belong at level 2. The Context diagram is for the conversation “here is the box, here is the world, here are the lines between them.” It is the diagram you put on the first page of a design doc, the one you show to a non-technical stakeholder, and the one that should be readable in fifteen seconds.
The actors are the humans. The system box is what we’re building. The external systems are dependencies we don’t control. Three external dependencies for a tiny SaaS is normal; a typical real system has anywhere from three to thirty depending on how mature the product is.
Level 2: Container
We zoom into the ChartSmith box and show what’s inside.
flowchart TB
user(["ChartSmith user"])
subgraph chartsmith ["ChartSmith"]
direction TB
spa["<b>Web app</b><br/><i>React, Vite</i>"]
api["<b>API</b><br/><i>Node.js, Express</i>"]
worker["<b>Export worker</b><br/><i>Python</i>"]
db[("<b>Database</b><br/><i>PostgreSQL</i>")]
cache[("<b>Cache</b><br/><i>Redis</i>")]
queue["<b>Job queue</b><br/><i>Redis + BullMQ</i>"]
end
subgraph external ["External services"]
direction TB
stripe["<b>Stripe</b>"]
sendgrid["<b>SendGrid</b>"]
s3["<b>AWS S3</b>"]
end
user --> spa
spa --> api
api --> db
api --> cache
api --> queue
queue --> worker
api --> stripe
api --> sendgrid
worker --> s3
classDef person fill:#e6f4f1,stroke:#0d9488,color:#0c1419
classDef ext fill:#fff5e6,stroke:#c89200,color:#5a3e00
classDef boundary fill:transparent,stroke:#0d9488,stroke-dasharray: 5 5
class user person
class stripe,sendgrid,s3 ext
class chartsmith,external boundary
This is the diagram developers and SREs argue over. It shows the actual deployable units, the technology choices for each, and the protocols between them. Six containers is a small system. A medium-sized SaaS will have ten to fifteen containers on this diagram. A large enterprise system will have so many that you split it into multiple Container diagrams, one per logical sub-system, and that’s fine.
A few conventions worth noting. Databases get the cylinder shape (ContainerDb in the Mermaid C4 syntax). Each container has a label, a technology, and a one-line description. Each arrow is labelled with what it does and what protocol it uses. Those labels feel like overkill the first time you draw the diagram and are essential the third time you look at it six months later wondering why on earth the API is talking to Redis.
The Container diagram is also where you can read the deployment topology. Each container in the diagram becomes one or more processes in production. The web app gets served from a CDN. The API runs as a fleet of Node processes behind a load balancer. The database is a managed Postgres. The cache is a managed Redis. The worker is a separate fleet of Python processes. None of that is in the diagram explicitly, but it’s all implied by which boxes exist.
Level 3: Component
We zoom into the API container and show the major modules inside it.
flowchart TB
spa["Web app"]
subgraph api ["API container"]
direction TB
router["<b>HTTP router</b><br/><i>Express, auth middleware</i>"]
subgraph features ["Feature components"]
direction LR
auth["<b>Auth</b>"]
charts["<b>Charts</b>"]
exports["<b>Exports</b>"]
billing["<b>Billing</b>"]
notifications["<b>Notifications</b>"]
end
data["<b>Data access</b><br/><i>Postgres + Redis repos</i>"]
end
storage[("DB / Cache / Queue")]
extsvc["Stripe / SendGrid"]
spa --> router
router --> auth
router --> charts
router --> exports
router --> billing
auth --> data
charts --> data
exports --> data
billing --> data
billing --> notifications
auth --> notifications
data --> storage
exports --> storage
notifications --> extsvc
billing --> extsvc
classDef ext fill:#fff5e6,stroke:#c89200,color:#5a3e00
classDef external fill:#f0f4f8,stroke:#5a6a73,color:#0c1419
classDef boundary fill:transparent,stroke:#0d9488,stroke-dasharray: 5 5
class extsvc ext
class spa,storage external
class api,features boundary
The Component diagram shows the API’s internal organisation. Seven components, each with a clear responsibility. The boundaries map onto folders in the repository, more or less. New developers joining the API team look at this diagram before they look at the code, because it tells them where to find things. “Where does Stripe get called?” Billing component. “Where is password hashing?” Auth component. “Where are SQL queries?” Data access component, and only there.
The convention that pays off here is that components should have single responsibilities and should communicate through clear interfaces. If you draw the Component diagram and find that every component talks to every other component, that’s a sign your container has lost its internal structure and the diagram is doing useful work by surfacing it.
You’d typically draw a Component diagram for each major container that’s complex enough to warrant one. The web app might get its own Component diagram showing the frontend’s structure. The export worker might get one showing the rendering pipeline. The database doesn’t need one. Components inside a database don’t really exist in a way C4 talks about.
Level 4: Code, and why we usually skip it
Level 4 would zoom into one component (say, the Auth component) and show the actual classes: UserRepository, SessionService, PasswordHasher, LoginController, and so on, with the relationships between them. UML class diagram territory.
In practice almost nobody draws these and almost nobody should. The reason is that level 4 diagrams are out of date the day you draw them. Class structures change every week as the team refactors. Maintaining a hand-drawn UML class diagram is a tax that nobody volunteers to pay, and IDE-generated class diagrams are accurate but rarely useful because they show every class with no curation.
The exception is when you’re documenting a particularly subtle piece of code: a state machine, a parser, a pattern that took someone three days to figure out and would take a future maintainer the same three days to re-derive without help. In those cases, a level 4 diagram earns its keep. Otherwise, your code is the level 4 diagram. Read it.
Brown’s own guidance is that levels 1 to 3 cover roughly 95% of useful architectural conversations. I’ve found that to be approximately right.
When not to use C4
C4 is not the right tool for every situation. A few cases where the convention is overkill or just wrong.
Very small systems. A single Lambda function backed by DynamoDB is two boxes. You do not need a Context diagram, a Container diagram, and a Component diagram for two boxes. One sketch is fine.
Very early prototypes. When you’re trying to decide whether an idea is even feasible, drawing formal C4 diagrams slows you down. A back-of-envelope sketch with handwritten boxes is better. C4 starts paying off once the system has shape and there are stakeholders to communicate with.
Specialised diagrams that have their own conventions. Sequence diagrams, state machines, network topology diagrams, ERDs, deployment diagrams, dataflow diagrams, BPMN process flows: these all have their own established conventions and C4 is not a replacement for them. C4 covers structure. It doesn’t cover behaviour, data shapes, or processes. Use the right tool for the question.
Teams that already have a different convention. If your organisation has standardised on UML 2 component diagrams, or ArchiMate, or a homegrown convention that everyone reads fluently, switching to C4 is mostly social cost. C4’s value is largely that everyone on the team uses it the same way, and any consistent convention beats no convention.
The general rule: use C4 when you’re explaining a non-trivial system to people who weren’t in the room when it was designed. Skip it when the audience and the artefact don’t justify the formality.
Tooling
A few tools that render C4 well in 2026.
Mermaid, in-markdown and supported in most modern documentation systems. The diagrams in this lesson are Mermaid C4. It supports C4Context, C4Container, C4Component, and C4Dynamic. The renderer is good enough for most uses, with occasional rough edges in layout. The big advantage: diagrams live in the same git repo as the code, version-controlled, and reviewed in pull requests.
Structurizr, Brown’s own tool. The defining feature is that you write the model once in a DSL and generate all the C4 levels from the same source. If you change a container, all the diagrams that include it update automatically. There’s a hosted version and a self-hosted Docker image. It’s the most “C4-native” tool there is, and if you’re going to invest deeply in C4, it’s worth knowing.
diagrams.net (formerly draw.io), with the C4 stencils enabled. Works well if you’re already using draw.io for everything else and want a quick visual editor. Less rigorous than Structurizr because the diagrams are hand-drawn and the model isn’t enforced anywhere, but the bar to entry is lower.
PlantUML with C4-PlantUML. Long-standing, text-based, integrates well with older toolchains. A bit more verbose than Mermaid but more featureful in some ways. Used widely in enterprise settings that have already invested in PlantUML for sequence diagrams and ERDs.
For most teams I’d recommend starting with Mermaid for diagrams that live in a documentation repo (the sweet spot of “version-controlled, reviewable, low ceremony”) and graduating to Structurizr if and when the diagrams start feeling like enough of a thing that managing them as a model rather than a set of pictures becomes worthwhile.
Diagram to create: A “C4 zoom levels” overview poster. Four nested rectangles, each labelled with the level (1 Context, 2 Container, 3 Component, 4 Code) and a short subtitle (System in the world, Containers inside the system, Components inside one container, Classes inside one component). To the right of each level, a one-line “audience” label: business stakeholders, developers and SREs, the team building the container, the implementer of the component. Use a consistent colour for each level (suggested: navy, teal, amber, slate). Title at top: “C4 model: four zoom levels for system diagrams.” Source attribution at the bottom: “After Simon Brown, c4model.com.”
What you should walk away from this lesson with
C4 is a convention with four zoom levels: Context (system in the world), Container (deployable units), Component (modules inside a container), Code (rarely drawn). Most useful diagrams live at levels 1 to 3. Each level has a clear audience and answers a clear question. The biggest practical win of using C4 is that conversations don’t drift between zoom levels, which is the failure mode that produces unreadable whiteboards.
Use Mermaid in your documentation repository as the path of least resistance. Use Structurizr if you want a single source of truth across diagrams. Skip C4 entirely for very small systems, very early prototypes, or specialised diagrams that have their own conventions.
Next lesson, we move from drawing systems to thinking about what’s actually running underneath them. Lesson 4 looks at the single machine: the OS, the process model, threads, async, file I/O, and what one box can do before you need a second one. The “scale up before you scale out” maxim only works if you know what scaling up actually buys you, and that’s where we’ll go.
References
- Simon Brown. The C4 model for visualising software architecture, https://c4model.com (retrieved 2026-05-01).
- Simon Brown. Software Architecture for Developers, vol. 2 (2014, with ongoing updates).
- Mermaid C4 syntax docs, https://mermaid.js.org/syntax/c4.html (retrieved 2026-05-01).
- Structurizr DSL reference, https://docs.structurizr.com/dsl (retrieved 2026-05-01).