6 min read
On this page

Architecture Diagrams

Most architecture diagrams fail because they try to show everything at once. A single diagram with every service, database, queue, cache, load balancer, and external dependency crammed into one view communicates nothing because it communicates everything. The fix is disciplined abstraction: show one level of detail at a time, answer one question per diagram, and use multiple simple diagrams instead of one complex one. The C4 model provides a practical framework for this discipline. It gives you four levels of zoom — context, container, component, and code — and the judgment to know which level a given audience needs.

The C4 Model

The C4 model, created by Simon Brown, is the most practical framework for technical architecture diagrams. It defines four levels of abstraction, each serving a different audience and answering a different question.

Level 1 — Context:    What is the system and what does it interact with?
Level 2 — Container:  What are the major deployable units?
Level 3 — Component:  What are the key building blocks inside a container?
Level 4 — Code:       What does the internal structure look like?

You rarely need all four levels. Most teams use Level 1 and Level 2 regularly, Level 3 occasionally, and Level 4 almost never (your IDE already shows code structure).

Level 1: System Context

The system context diagram is the most zoomed-out view. It shows your system as a single box surrounded by the users and external systems it interacts with.

What it answers:
  "What is this system and who/what uses it?"

What it contains:
  - Your system (one box)
  - Users/personas who interact with it
  - External systems it depends on or feeds into
  - Labeled arrows showing the nature of each interaction

What it does NOT contain:
  - Internal details of your system
  - Databases, queues, or infrastructure
  - Deployment topology

This is the diagram you show to non-technical stakeholders, new team members, and anyone asking "what does this system do?" If your context diagram has more than 6-8 boxes, you are showing too much detail.

Example context diagram elements for an e-commerce platform:

  [Customer]  -- "browses products, places orders" --> [E-Commerce Platform]
  [E-Commerce Platform]  -- "processes payments" --> [Stripe]
  [E-Commerce Platform]  -- "sends notifications" --> [SendGrid]
  [E-Commerce Platform]  -- "ships orders" --> [Warehouse System]
  [Admin User]  -- "manages inventory, views reports" --> [E-Commerce Platform]

Five elements, four relationships. The reader understands the system's boundaries and interactions in seconds.

Level 2: Container

The container diagram zooms into your system and shows the major deployable/runnable units: applications, services, databases, message queues, file storage.

What it answers:
  "What are the high-level technology choices and how do the
   major pieces communicate?"

What it contains:
  - Web applications, mobile apps, APIs
  - Databases (with technology labels)
  - Message brokers, caches
  - Arrows showing communication protocols (HTTP, gRPC, AMQP)

What it does NOT contain:
  - Internal classes or modules
  - Individual API endpoints
  - Infrastructure details (VPCs, load balancers)
Example container diagram elements:

  [React SPA] -- "HTTPS/JSON" --> [API Gateway (Kong)]
  [API Gateway] -- "gRPC" --> [User Service (Go)]
  [API Gateway] -- "gRPC" --> [Order Service (Go)]
  [API Gateway] -- "gRPC" --> [Inventory Service (Java)]
  [User Service] -- "TCP" --> [PostgreSQL (users)]
  [Order Service] -- "TCP" --> [PostgreSQL (orders)]
  [Order Service] -- "TCP" --> [Redis (cache)]
  [Inventory Service] -- "TCP" --> [MongoDB (inventory)]
  [Order Service] -- "AMQP" --> [RabbitMQ]
  [Inventory Service] -- "AMQP" --> [RabbitMQ]

This diagram answers: "What are the moving parts and how do they talk to each other?" It is the diagram most engineering teams use day-to-day.

Level 3: Component

The component diagram zooms into a single container and shows its internal building blocks — the major modules, services, or layers within one deployable unit.

What it answers:
  "How is this service organized internally?"

What it contains:
  - Major modules, packages, or layers within one container
  - Internal communication between components
  - Connections to external containers (databases, other services)

When to use it:
  - Onboarding a new developer to a specific service
  - Design discussions about restructuring a service
  - Documenting a complex service that has distinct internal boundaries

Most teams do not create component diagrams for every service. Only diagram the internals when a service is complex enough that the structure is not obvious from the code.

Level 4: Code

The code-level diagram shows classes, interfaces, and their relationships within a single component. In practice, this level is almost never worth maintaining manually — it changes too frequently and your IDE already generates it on demand. Reserve Level 4 for documenting particularly complex class structures in architecture decision records.

One Level of Abstraction

The most important rule for architecture diagrams: stay at one level of abstraction. Do not mix levels.

Mixed levels (confusing):
  [React SPA] --> [API Gateway] --> [UserController class] --> [PostgreSQL]

  [The SPA and API Gateway are containers (Level 2).
   UserController is code (Level 4).
   PostgreSQL is a container (Level 2).
   Three levels in one diagram.]

Consistent level (clear):
  [React SPA] --> [API Gateway] --> [User Service] --> [PostgreSQL]

  [All containers. One level. The reader knows what they are looking at.]

When you mix levels, the reader cannot tell what is a deployable unit and what is an internal detail. They do not know which boxes they can independently scale, deploy, or replace.

How to Enforce One Level

Before adding a box to your diagram, ask: "Is this at the same level of abstraction as the other boxes?" If your other boxes are services (containers), do not add a class. If your other boxes are classes (code), do not add a database server.

Level    Box examples
────────────────────────────────────────
Context  "E-Commerce Platform", "Payment Provider"
Container  "User Service", "PostgreSQL", "Redis", "React SPA"
Component  "AuthMiddleware", "OrderRepository", "PricingEngine"
Code     "UserController", "OrderService interface", "PriceCalculator"

Labeling Everything

An unlabeled diagram is an ambiguous diagram. Every element needs a label. Every arrow needs a label.

Box Labels

Every box should have:

Required:
  - Name (what it is)
  - Technology in brackets (what it runs on)

Optional but helpful:
  - Brief description of responsibility

Examples:
  [User Service (Go)]
  [PostgreSQL (v15, users database)]
  [Redis (session cache, 6.2)]
  [React SPA (Next.js 14)]

Arrow Labels

Every arrow should describe what flows along it. An unlabeled arrow between two boxes could mean any of a dozen relationships.

Bad:
  [Service A] --> [Service B]
  [What is this? A dependency? A data flow? A deployment order?]

Good:
  [Service A] -- "sends order events via gRPC" --> [Service B]
  [Service A] -- "reads user profiles, HTTPS/REST" --> [Service B]
  [Service A] -- "publishes to orders.created topic" --> [Kafka]

The arrow label should include both what is communicated and how (the protocol or mechanism).

Consistent Visual Language

Use shapes and colors consistently within a diagram and across related diagrams.

Common conventions:

  Rectangles         — services, applications
  Cylinders          — databases, storage
  Rounded rectangles — external systems
  Dashed borders     — optional or planned components
  Solid arrows       — synchronous communication
  Dashed arrows      — asynchronous communication

Color conventions:
  Blue    — your system's components
  Gray    — external systems you do not control
  Green   — databases and storage
  Orange  — message brokers and queues

The specific conventions matter less than consistency. If blue means "our services" in one diagram, it should mean the same in every diagram in your documentation. Document your conventions in a legend if they are not self-evident.

One Question Per Diagram

Every diagram should answer exactly one question. If a diagram is trying to answer multiple questions, split it.

One diagram trying to answer too many questions:
  "How do requests flow through the system AND how is it deployed
   AND what are the failure modes AND what are the data stores?"

Split into focused diagrams:
  Diagram 1: "How does a customer request flow through the system?"
  Diagram 2: "What infrastructure runs each service?"
  Diagram 3: "What happens when the order service is unavailable?"
  Diagram 4: "Where is data stored and how does it replicate?"

Each focused diagram is simpler, clearer, and easier to maintain. The reader finds the answer they need faster because they go to the diagram that answers their specific question.

Multiple Simple Diagrams

This follows directly from the "one question" rule. A system of moderate complexity might need 4-6 diagrams rather than 1-2 overloaded ones.

Typical diagram set for a backend service:

  1. System context — what this service is and what it interacts with
  2. Container overview — the deployable units and their communication
  3. Request flow — how a typical request moves through the system
  4. Data flow — how data is ingested, processed, and stored
  5. Deployment — where each container runs (which cluster, VPC, region)

Each diagram is simple enough to understand in under 30 seconds. Together, they provide a complete picture. No single diagram tries to be the complete picture by itself.

Practical Tips

Keep diagrams to 10-12 boxes and 15-20 arrows maximum. If you need to zoom in to read labels, the diagram is too complex. Use left-to-right flow for requests and data, top-to-bottom for hierarchies. Be consistent within a diagram.

Treat diagrams like code: store sources in version control, update them when the system changes, include diagram review in pull request checklists, and delete diagrams for systems that no longer exist. An outdated diagram is worse than no diagram because it actively misleads.

Common Pitfalls

  • The "one diagram to rule them all" approach. One massive diagram showing everything is useless. Split into multiple focused diagrams at consistent abstraction levels.
  • Mixing abstraction levels. A diagram that shows both services (containers) and classes (code) in the same view confuses the reader about what they are looking at.
  • Unlabeled arrows. An arrow without a label is ambiguous. Label every connection with what flows and how.
  • Inconsistent visual language. If boxes mean different things but look the same, the reader cannot tell them apart. Use consistent shapes, colors, and conventions.
  • Diagrams that answer multiple questions. Each diagram should answer one question. If you are showing architecture and deployment and data flow in one diagram, split it into three.
  • Never updating diagrams. An architecture diagram from 18 months ago that does not match the current system is actively harmful. Update or delete.
  • Over-diagramming simple systems. A service with one database and one external dependency does not need a C4 deep-dive. A sentence or two suffices.

Key Takeaways

  • Use the C4 model: context, container, component, and code — four zoom levels, each for a different audience.
  • Stay at one level of abstraction per diagram. Never mix containers and classes in the same view.
  • Label every box (name + technology) and every arrow (what flows + protocol).
  • One diagram, one question. If your diagram tries to answer multiple questions, split it.
  • Multiple simple diagrams beat one complex diagram. Aim for 10-12 boxes maximum per diagram.
  • Use consistent shapes and colors. Document your conventions if they are not self-evident.
  • Treat diagrams like code: version them, review them, update them, delete them when they rot.