Skip to content

Technical Design

Read this before writing docs/design.md or when integration keeps surprising you; it gives you what a design document must decide and how to keep it current.

Technical design is the process of deciding how your system will be built, before or alongside building it. It bridges the gap between requirements (what the system must do) and implementation (how it actually does it). Without deliberate design work:

  • Teams make inconsistent decisions that accumulate into architectural debt.
  • Interfaces between components are discovered at integration time, not before.
  • Onboarding a new contributor requires spelunking through the codebase rather than reading a document.
  • Risks that could have been caught on paper get caught in production instead.

Technical design is not about producing perfect diagrams before writing a line of code. It is about thinking through the hard parts before they become expensive to change.

Technical design answers three questions:

  1. What are the major components, and how do they interact? (Architecture)
  2. What are the interfaces and contracts between them? (Interface design)
  3. What are the riskiest assumptions, and how will we validate them? (Risk and prototyping)

The output is a design document: a written artifact that captures decisions, diagrams, and rationale. It does not need to be exhaustive. It needs to be useful.

The design document captures the big picture. Individual decisions along the way (why PostgreSQL over MongoDB, why a monolith instead of microservices, why this particular data pipeline architecture) are recorded as Architecture Decision Records (ADRs). The two artifacts complement each other: the design document shows what the system looks like; ADRs explain why it looks that way.

Technical design operates at several levels of abstraction. Most projects benefit from thinking through at least the first two.

The highest-level view: what are the major subsystems, services, or modules, and how do they communicate? The C4 model provides a useful framework for thinking about this at different levels of zoom:

  • Context diagram (C4 Level 1): your system as a black box, showing who and what interacts with it (users, external services, data sources, hardware). This is the diagram for your project partner and for anyone non-technical.
  • Container diagram (C4 Level 2): the major deployable or runnable units and how they communicate. For a web application, these might be a frontend, an API server, and a database. For a data pipeline, they might be an ingestion service, a processing framework, and a storage layer. For an embedded project, they might be firmware modules and a host application.
  • Component diagram (C4 Level 3): the internal structure of a single container, for the developers working inside it.
  • Code diagram (C4 Level 4): the implementation details of one component, usually a UML class diagram or an entity-relationship diagram. Treat it as optional; an IDE can generate it on demand, and hand-drawn copies go stale within a sprint.

You do not need all four levels. A context diagram plus one level of decomposition is sufficient for most Capstone projects. The goal is to give any reader, including your project partner, a mental model of the system without reading the code. The C4 model is a way to structure diagrams, not a notation; draw the boxes in whatever tool your team already uses.

Philippe Kruchten’s 4+1 Architectural View Model predates C4 and answers a different question: not “how far do we zoom in” but “whose concerns does this view address”. It splits the description into five concurrent views:

  1. Logical view: the functionality the end user sees, usually class or state diagrams.
  2. Process view: the runtime behavior, including concurrency, communication between processes, and performance.
  3. Development view: the codebase from the programmer’s side; package diagrams showing how modules are organized.
  4. Physical view: the deployment onto hardware; the UML deployment diagram.
  5. Scenarios (the +1): a handful of use cases traced through the other four views. They validate the design and act as a consistency check.

Use 4+1 when your readers have distinct concerns (a partner who cares about deployment, a maintainer who cares about module boundaries). Use C4 when the question is depth. Do not produce both for a Capstone project.

Wherever two components meet, there is an interface: a contract that defines what each side provides and expects. Making these contracts explicit early lets team members work in parallel, even before the implementations exist.

What “interface” means depends on the project:

  • In a web or mobile application, interfaces are typically API endpoints (paths, methods, request/response schemas, error codes) and database schemas (tables, relationships, constraints).
  • In a data pipeline or research project, interfaces are the stage boundaries: what format does the output of one stage take, and what does the next stage expect? A CSV with specific columns, a serialized model artifact, a set of image files in a particular directory structure.
  • In a library or framework contribution, the interface is the public API: which functions, classes, or modules are exposed, and what are their signatures and guarantees?
  • In any project with asynchronous communication, interfaces include event or message formats: what gets published, what gets consumed, and what happens when a message is malformed or delayed.

The documentation does not need to be elaborate. A Markdown table, an OpenAPI specification, or even a well-commented type definition file can serve as the contract. What matters is that the team agrees on the boundary before building toward it from both sides.

For most projects, how data is modeled is among the most consequential early decisions. Changes to the data model late in a project are expensive, whether that means database migrations and rewritten queries or reformatted datasets and invalidated experimental results. Sketch your data model early, even informally.

The questions worth asking depend on the project, but a few are nearly universal:

  • What are the core entities or data objects, and how do they relate to each other?
  • What data must be persisted vs. derived at runtime or recomputed on demand?
  • What data has privacy, compliance, or NDA implications?

For projects with a database, this means entity-relationship diagrams, table schemas, and indexing strategy. For research or data-intensive projects, the equivalent questions are: where does the data come from, how is it preprocessed, what format is it stored in, and how will someone else reproduce the pipeline from raw input to final output? Versioning datasets (or at minimum, documenting the source, retrieval date, and preprocessing steps) is as important for reproducibility as version-controlling code.

Sometimes the right approach is to build a small, throwaway prototype to answer a specific question before committing to a design: Can we integrate with this third-party API? Is this library fast enough for our use case? Does this algorithm generalize to real data?

This is called a spike in agile terminology. A spike is time-boxed (1 to 3 days) and produces a finding, not production code. Document what you learned in an ADR and discard the prototype code.

The best technical designs are informed by spikes on the riskiest assumptions. If you have a component you have never built before, spike it first.

A design document is not a form to fill out. It is a narrative that helps someone understand how the system works (or will work) without reading the code. A good design document answers questions that the team and its project partner actually have, and it does so concisely enough that people will read it more than once.

The useful ones cover the same ground: what the system does, the architecture as one or two diagrams, the interfaces between components (where misunderstandings do the most damage), the data and how it flows, and the parts that are uncertain enough to be a risk. The document below shows all of it in two pages.

For a software project, that means a context diagram, an API specification, a database schema, and a list of technical unknowns. For a research project, the equivalent might be a computational workflow diagram, a description of the data pipeline stages and their input/output formats, a reproducibility plan, and a list of experimental assumptions that need validation. For a FOSS contribution, the focus shifts to understanding the existing architecture and explaining where and how the proposed change fits in.

The design document should also reference the team’s ADRs. Every significant “why” in the design (why this database, why this architecture, why this algorithm) should have a corresponding ADR that captures the alternatives considered and the reasoning. The design document shows the current state; the ADRs show how and why it got there.

A docs/design.md that answers those questions in two pages looks like this:

# Design
## Overview
Shift board for <partner>: a React front end, a Node API, and Postgres
on Render. Coordinators and volunteers sign in with the partner's
Google Workspace. Requirements: docs/requirements.md.
## Architecture
![C4 container diagram](./diagrams/containers.png)
- Web app (React): the board, the claim and release flows.
- API (Node, Express): auth check, shift rules, notifications.
- Postgres, managed by Render: shifts, claims, users.
- Email (Postmark), called from the API on release.
## Interfaces
- REST, JSON. Spec in docs/api/openapi.yaml; the client is generated
from it. POST /shifts/{id}/claims returns 409 when the shift is full.
## Data
- Entities: User, Shift, Claim (ERD in ./diagrams/erd.png).
- One Claim per (shift, user), enforced by a database constraint.
## Deployment
- Render web service plus managed Postgres; a preview deploy per PR.
- Rollback: redeploy the previous build from the Render dashboard
(tried once, works). Production target agreed with the partner: ADR-004.
## Risks and open questions
- Google Workspace SSO on the partner's tenant is unproven. Spike in
sprint 2; owner on the board.
- Email deliverability from a new domain. Fallback: in-app notice only.
## Decisions
ADR-001 Postgres over SQLite. ADR-002 REST over GraphQL. ADR-004 Render.

If you want a template rather than a blank page, arc42 is the standard one: twelve sections covering goals, constraints, context, building blocks, runtime, deployment, cross-cutting concepts, decisions, quality, risks, and glossary. It prescribes structure, not notation, so C4 or UML diagrams drop into it unchanged. Take the sections you need and delete the rest; a Capstone design document that fills all twelve is padding.

UML (Unified Modeling Language) provides a standard vocabulary for describing software structure and behavior. Useful diagram types:

  • Sequence diagram: how components interact over time for one scenario. The one most Capstone teams actually need, usually for the authentication or the main data flow.
  • State diagram: the lifecycle of an entity with distinct states (an order, a job, a claim).
  • Class or entity-relationship diagram: static structure; generate it from the code or the schema rather than drawing it.
  • Deployment diagram: what runs where, which the deployment section of your design document already needs.

Activity, component, package, and composite-structure diagrams exist too; the UML diagram reference covers all of them. Pick the diagrams that answer the questions your team is currently uncertain about. A sequence diagram for your authentication flow is more valuable than a complete UML suite nobody maintains. Never create a diagram just because a template requires it.

A design that has never been challenged is just an assumption. Validate it by:

  • Reviewing it with your project partner: does the architecture support their use cases and deployment constraints?
  • Walking through a key scenario end-to-end: trace a representative workflow (a user action, a data processing run, an experimental trial) through every component in your diagram. Where are the gaps?
  • Asking “what if this component fails?”: how does the system degrade gracefully?
  • Running a spike on the riskiest assumption: if a component or integration is high-risk, build a throwaway prototype before committing to it.

The design is working when the things it was supposed to prevent stop happening:

  • Integration surprises: two components built separately meet and work. If the sprint before a demo is spent reconciling interfaces, the interface section was not specific enough.
  • Onboarding time: a new teammate, a reviewer, or an AI tool with the document in context can find its way around the code without a walkthrough.
  • Decisions stick: an approach the team rejected does not get re-proposed, by a person or a tool, because the ADR says why.
  • The diagram is still true: if the architecture drawing and the code disagree, the document has stopped being read.

Your design will change; that is normal. The goal is not to freeze it in Fall term and execute blindly. The goal is to ensure design changes are deliberate, communicated, and documented rather than happening implicitly as team members make independent local decisions.

When the design changes significantly, update the document and write an ADR explaining the change, the alternatives considered, and the rationale.

AI coding tools have changed how individuals write software, but they have not changed the need for technical design. If anything, they have made it more important.

Working alone, the design document can feel unnecessary: the AI reads the codebase, infers the patterns, and generates code that fits. The codebase is the design, and the tool follows it well enough.

Three or four people each working with their own tool is a different problem. Every assistant operates in its own local context: the files it was shown, the conversation it had, the instructions it was given. It does not know what a teammate’s tool suggested yesterday, what the team agreed in a meeting, or which approach was considered and rejected. Each human-AI pair drifts toward its own reading of how the system should work, and the codebase grows fast and incoherent: one member’s tool adds an abstraction layer where another’s used a utility function, naming diverges, error handling multiplies. It compiles, the tests pass, and by the time anyone notices, the inconsistency is embedded.

The design document is the antidote, because it gives every teammate and every tool the same account of how the system is structured and what boundaries to respect. Four practices carry it:

  • Distill it into your project-level instructions, so every suggestion is at least aware of your architecture, naming, and interface contracts. AI Project Setup has the files and what belongs in them.
  • Use ADRs to record what was rejected, not just what was chosen. AI tools have no memory of your team’s past discussions. Without an ADR that says “we considered a microservices architecture and rejected it because of deployment complexity,” an AI assistant may cheerfully suggest exactly the approach your team already ruled out.
  • Review AI-generated code against the design, not just against correctness. Code that works but violates the agreed architecture is not done. The design document gives reviewers a basis for catching structural drift before it accumulates.
  • Treat the design document as onboarding material for your AI tools. Just as you would walk a new team member through the architecture before they start contributing, give your AI tools the same context. The more explicit and accessible the design, the better the AI’s suggestions will align with it.

None of this applies to a spike. The point of a prototype is to try things, learn fast, and throw the result away, so let the tool run. The discipline is for code meant to last: merged to main, deployed, or built on by someone else.

The irony is that a team using AI tools needs more design discipline for production code, not less, because mistakes propagate faster. A person writing by hand produces one inconsistent module in a week; a tool produces five in an afternoon.

The design document addresses the architectural side of AI coordination: what constraints and patterns to enforce. The process side (which tools the team uses, how AI-generated code is reviewed, how AI-suggested changes are evaluated) belongs in the team’s working agreement.

  • Design for your current phase, not for a hypothetical future scale. Over-engineering is a real risk in a 9-month project.
  • Separate concerns. If a component diagram has too much in one box, break it apart.
  • Make implicit dependencies explicit. If service A only works when service B is running, say so.
  • Treat interface definitions as more stable than implementations. Protect your contracts; change your implementations freely.
  • Include a section on what was not designed: explicitly out-of-scope components or deferred decisions. This shows intentionality.
  • Write the design document for the next person joining the project cold, not for the team that already knows everything.

No design survives contact with implementation, and the honest version of that is stronger than it sounds: the document will be wrong, and its value is almost entirely in what the team understood while writing it. This has a practical implication people miss. If the value is in the thinking, then a design document written by one person the night before it is due has produced almost none of the benefit, however good the document is. The artifact is evidence of a conversation, and a team that skips the conversation and produces the artifact has optimized for the wrong thing.

Over-design is the more common failure on capstone teams, not under-design, and it has a recognizable shape: a plugin architecture for the one plugin that exists, an abstraction layer over a database you will never swap, microservices for a system four people are building. Fowler’s YAGNI is the argument to read, and his MonolithFirst is the specific version for the decision you are most likely to get wrong. Both make the same point: speculative generality costs you now and pays off only in a future that usually does not arrive.

There is a real disagreement here worth naming, because this guide takes a side. The “big design up front” tradition says decide the architecture before you build; the agile reaction says let it emerge from the code. Neither works unmodified for a project with a nine-month clock and a partner. What this guide recommends is narrow: decide in advance only the things that are expensive to change later (data model, authorization boundaries, deployment topology, integration contracts), and let everything else emerge. The test for the list is not importance but reversibility.

A diagram drawn in twenty minutes genuinely does prevent days of rework, and the reason is that it makes disagreement visible. Four people can say “the API talks to the database” and hold four different pictures of what that means, and the words will not surface the difference. A boxes-and-arrows sketch will, usually within a minute, because someone points at a box and says that is not where that lives. The C4 model is worth adopting if you want a convention rather than inventing one per meeting, and its main contribution is insisting that you say which level of zoom a diagram is at.

The last one is uncomfortable and worth saying anyway. Your architecture will come to resemble your team’s communication structure whether you plan it or not, which is Conway’s law. On a four-person team where two people never talk, you will get a seam exactly there, and it will not be the seam the design document specified. If the design assumes coordination your team does not actually do, the code will follow the team.

In industry, technical design documents go by many names: Tech Spec, RFC (Request for Comments), Design Doc, TDD (Technical Design Document). Google, Amazon, and most large software companies require design documents for non-trivial changes. They serve as the basis for code review, stakeholder alignment, and future reference.

Amazon’s Working Backwards methodology starts with the customer (the press release and FAQ) before the design. The design document is where the “how” is evaluated before engineering investment is committed.

In research settings, the closest equivalent is the methods section of a paper, describing the approach in enough detail for replication. For computational research, this includes architectural and algorithmic decisions sufficient to reproduce results.

Architecture writing skews heavily toward systems far larger than yours, so the entries below are chosen for a small team with a fixed deadline. The C4 model and arc42 are the two conventions worth knowing; the Fowler pieces are the arguments against building more than you need.

Sources and further reading

  • The C4 model, Simon Brown: context, container, component, and code, and why saying which level you are drawing matters more than notation.
  • arc42: a proven template for architecture documentation, if you want more structure than this guide’s outline.
  • Software Architecture Guide, Martin Fowler: what architecture means and why it is worth deciding deliberately.
  • Yagni and MonolithFirst, Martin Fowler: the two arguments most likely to save your team from over-design.
  • Conway’s law: why your system’s seams end up where your team’s communication gaps are.
  • Architecture Decision Records: the companion artifact, for the decisions this document leaves out.
  • Azure Architecture Center, Microsoft: reference architectures and patterns, useful as a catalog of what a shape is called.

Activities that exercise this