Conway's Law: How Team Structure Shapes Architecture
Systems copy the communication structure of the organization that designs them: the same 14 commits need more than one team 86% of the time with layer teams and 21% with flow teams. Conway (1968), the Inverse Conway Maneuver, Team Topologies' team types and interaction modes, cognitive load, and two different right architectures for Bookstore with a team of 3 and with four stream-aligned teams.
Version note: The code in this post was built and run with C# 14 and .NET 10.
Guess First
We took the last 14 commits from Bookstore's Git history. The code has the layered structure it has today: the Bookstore.Web, Bookstore.Business and Bookstore.Data projects, each with subfolders like Catalog, Ordering and Inventory. The commits are ordinary business requests: a publication year filter on the search page, partial order cancellation, reserving stock at checkout, a shipment tracking page, an email when the order ships.
Now we will evaluate the same 14 commits under three different team structures. The first is today: 3 developers, one team. The second is the structure that comes to mind first as the company grows: a web team, a backend team, a data team. The third is four teams built around business flows: Catalog & Search, Ordering & Payment, Fulfillment (Inventory and Shipping), Customer Experience (Notification and accounts).
In each structure, what percentage of commits change files owned by more than one team? In other words, what percentage require more than one team to talk, plan together or wait for each other? Guess three numbers. The code does not change at all; only which file belongs to which team does.
Conway's Law: A System Copies the Organization That Designs It
Melvin Conway put forward this thesis in "How Do Committees Invent?", published in Datamation in April 1968: organizations that design systems are constrained to produce designs that copy their own communication structures. Conway explains it with a mathematical analogy: if you draw the parts of the system and their interfaces as one graph, and the design groups and the communication channels between them as another, there is a structure-preserving mapping (homomorphism) between the two. Where two groups do not talk, it is hard for two parts to talk to each other.
The paper also says this: setting up the design organization means making some design decisions up front. The moment you split teams, you have also made the first decision about which parts the system will be split into. Fred Brooks referred to the idea as Conway's Law in The Mythical Man-Month in 1975, and the name stuck.
In his Conway's Law article from October 2022, Martin Fowler says the law has affected every system he has seen and that fighting it is usually a losing battle. The examples in the article make it concrete: a compiler written by one team has one pass, one written by two teams has two; someone acting as architect for six teams in six different cities decides up front on six subsystems with narrow, well-defined interfaces between them, because distance and time zones restrict conversation. Fowler has another important note: a dozen or two people who talk to each other informally will naturally produce a monolith, and that is fine. The law matters to the degree that people need organizing.
Measuring It: Coordination Cost from Commit History
To keep Conway's Law from staying abstract we wrote a small tool. It reads the repository's commit history with git log, maps each file to a team according to the rules in an ownership file, and counts the commits that touch more than one team. The ownership rules work like GitHub's CODEOWNERS file: of the rules that match a file, the last one wins.
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// Usage: dotnet run -- <repo> <owners-file>
string repo = args[0];
List<(Regex Pattern, string Team)> rules = File.ReadLines(args[1])
.Where(line => line.Trim() is { Length: > 0 } t && !t.StartsWith('#'))
.Select(line => line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
.Select(parts => (GlobToRegex(parts[0]), parts[1]))
.ToList();
List<Commit> commits = ReadCommits(repo);
int crossTeam = 0;
Dictionary<string, int> pairCounts = [];
foreach (Commit commit in commits)
{
// As in CODEOWNERS: the last rule matching the file wins
string[] teams = commit.Files
.Select(file => rules.LastOrDefault(r => r.Pattern.IsMatch(file)).Team ?? "(unowned)")
.Distinct()
.Order()
.ToArray();
if (teams.Length > 1)
{
crossTeam++;
string pair = string.Join(" + ", teams);
pairCounts[pair] = pairCounts.GetValueOrDefault(pair) + 1;
}
}
Console.WriteLine($"Commits: {commits.Count}, needing more than one team: {crossTeam} ({(double)crossTeam / commits.Count:P0})");
foreach ((string pair, int count) in pairCounts.OrderByDescending(p => p.Value).ThenBy(p => p.Key))
Console.WriteLine($" {count,2} x {pair}");
static List<Commit> ReadCommits(string repo)
{
ProcessStartInfo psi = new("git", ["-C", repo, "log", "--name-only", "--pretty=format:@%h %s"]) { RedirectStandardOutput = true };
using Process git = Process.Start(psi)!;
List<Commit> result = [];
Commit? current = null;
while (git.StandardOutput.ReadLine() is { } line)
{
if (line.StartsWith('@')) result.Add(current = new Commit(line[1..], []));
else if (line.Length > 0) current!.Files.Add(line);
}
git.WaitForExit();
return result;
}
static Regex GlobToRegex(string glob) =>
new("^" + Regex.Escape(glob).Replace(@"\*\*/", "(.*/)?").Replace(@"\*\*", ".*").Replace(@"\*", "[^/]*") + "$");
sealed record Commit(string Title, List<string> Files);
A slice of the commit history shows what the tool sees:
@2e4654c Email when order ships
src/Bookstore.Business/Notification/EmailService.cs
src/Bookstore.Business/Shipping/ShipmentService.cs
@4396d9d Low stock alert page
src/Bookstore.Business/Inventory/StockService.cs
src/Bookstore.Data/Inventory/StockRepository.cs
src/Bookstore.Web/Inventory/AlertsPage.cs
Three ownership files, three team structures:
# Today: 3 developers, one team
src/** bookstore-team
# Teams by layer
src/Bookstore.Web/** web-team
src/Bookstore.Business/** backend-team
src/Bookstore.Data/** data-team
# Teams by flow (domain)
**/Catalog/** catalog-search-team
**/Ordering/** ordering-payment-team
**/Payment/** ordering-payment-team
**/Inventory/** fulfillment-team
**/Shipping/** fulfillment-team
**/Notification/** customer-experience-team
Same repository, same 14 commits, three runs:
for o in owners-single-team owners-by-layer owners-by-stream; do echo "== $o"; dotnet run -- ../repo ../$o.txt; done
== owners-single-team
Commits: 14, needing more than one team: 0 (0 %)
== owners-by-layer
Commits: 14, needing more than one team: 12 (86 %)
8 x backend-team + data-team + web-team
3 x backend-team + data-team
1 x backend-team + web-team
== owners-by-stream
Commits: 14, needing more than one team: 3 (21 %)
1 x (unowned) + ordering-payment-team
1 x customer-experience-team + fulfillment-team
1 x fulfillment-team + ordering-payment-team
With one team the coordination cost is zero; everyone is at the same table anyway. With layer teams 86% of commits need more than one team, and 8 of the 14 commits are changes all three teams touch. A small request like "a publication year filter on the search page" changes the web team's endpoint, the backend team's service and the data team's query: three backlogs, three sprint plans, two handoffs. With flow teams the ratio drops to 21%, and the three remaining commits are work that really joins two business areas: reserving stock at checkout (Ordering and Inventory), an email when the order ships (Shipping and Notification), and a migration.
This result is the organizational side of the technical versus domain partitioning distinction from the sixth post. Because the code is split into layers, building layer teams seems natural, but Bookstore's changes come from business areas and each one cuts vertically through the layers. Conway's Law works in both directions here: layer teams cement the layered architecture, and the layered architecture makes layer teams look justified. The example is small and we picked the commits, so do not generalize the percentages. But if you run the same tool on the last six months of your own repository, you will see for the first time, in numbers, how much coordination each team structure costs you.
Notice the (unowned) line in the third output too. The migrations folder belongs to no team; the "Add order note field" commit touches both the Ordering team's files and a file with no owner. Unowned code is the code that rots fastest when the team structure changes: everyone touches it, nobody takes responsibility for it.
The Inverse Conway Maneuver
If the law is an observation, you can also use it as a tool. The Inverse Conway Maneuver means changing the team structure and communication paths first in order to get the architecture you want. Jonny LeRoy and Matt Simons used the term in an article in the December 2010 issue of Cutter IT (Information Technology) Journal; Fowler's Conway's Law article gives the source that way too.
Fowler lists three responses to Conway's Law. Ignoring it, which he sees as a mistake. Accepting it: designing the architecture so that it does not clash with the existing communication structure. And the inverse maneuver: changing the communication structure so that it encourages the desired architecture. From this angle he sees microservices first of all as an organization design tool: small, long-lived teams built around a business capability naturally produce services that can be deployed independently.
His warning is clear as well. The inverse maneuver is not all-powerful: changing the organization does not quickly fix an established, rigid architecture, and it can add friction on top. Changes have to be made in small steps, watching the feedback. In Bookstore's terms: on the day you set up the four flow teams, the code will still be layered. The teams will gradually pull the code in their areas into modules; the architecture will follow the team structure a few months behind.
Team Topologies: Four Team Types, Three Interaction Modes
Matthew Skelton and Manuel Pais's book Team Topologies (IT Revolution, 2019; second edition September 2025) puts Conway's Law at the center of team design and splits teams into four fundamental types. A stream-aligned team is aligned to a flow of value belonging to part of the business domain; Bookstore's four flow teams are of this type, and most teams in an organization should be. An enabling team removes obstacles in front of stream-aligned teams and spots missing capabilities, works alongside them for a while and then steps back. A complicated-subsystem team owns a subsystem that needs serious expertise (mathematics, calculation, a specialized technical area). A platform team provides an internal product that speeds up other teams' work: the deployment pipeline, observability tools, hosting.
Relationships between teams are limited to three modes as well. Collaboration: two teams work together for a defined period to discover something new. X-as-a-Service: one team provides something and the other consumes it like a service. Facilitation: one team helps and coaches another. The reason to choose the modes deliberately is Conway: if two teams are permanently in collaboration mode, their responsibilities and their code get tangled; an X-as-a-Service relationship requires a clear interface between the two teams.
Bookstore's 18-month goal has four stream-aligned teams and one platform team. The platform team provides the CI (Continuous Integration) pipeline, environments and monitoring tools as X-as-a-Service. A collaboration period of a few weeks between Ordering & Payment and Fulfillment makes sense until the stock reservation rules settle; once the boundary is in place, the relationship should turn into X-as-a-Service. On the security side, a small enabling team or role could support the teams in facilitation mode to spread OWASP (Open Worldwide Application Security Project) practices.
Cognitive Load: How Much System Can a Team Carry?
One of the most useful concepts in Team Topologies is cognitive load. The book takes three types from cognitive load theory in psychology: intrinsic load (the fundamental skills of the work itself), extraneous load (the mechanical load around the work: tools, environments, deployment steps) and germane load (the mental effort spent on the domain knowledge that produces the real value). The goal is to reduce extraneous load with a platform and good tools and free the team's capacity for germane load.
The architectural rule that follows is simple: the size of the software given to a team must fit the team's mental capacity. A team must have the capacity to both build and run the services it owns. There is no exact measure for this, but teams can be compared with each other: a discovery-heavy, constantly changing domain (Ordering & Payment in Bookstore) brings more load than a domain made of repetitive work. One of the practical methods Skelton and Pais suggest is asking the team regularly to score from 1 to 5 how well they understand their system; a falling score points either to a person who needs support or to a problem in the architecture.
That is why "team structure" and "architecture" are not separate decisions. For Bookstore's current team of 3, the whole system is one unit of cognitive load, and one monolith is the right answer. With four teams, each team will need a part it can carry, and the boundary of that part will run along what Team Topologies calls fracture planes, the natural seams: business area boundaries, different rates of change, or different compliance requirements.
Bookstore: Same System, Two Right Architectures
Today Bookstore has 3 developers and a part-time ops person. Everyone touches everything, daily conversation resolves every coordination, and the coordination cost we measured is zero. For this team the right architecture is a single deployment unit: layered or, preferably, a modular monolith. Giving this team five services would be working against Conway: you would split the system with an operational load a single team cannot carry, and the service boundaries would have no counterpart in the team's communication structure.
In 18 months, with four stream-aligned teams and one platform team, the right answer for the same system changes. First comes module ownership: each team owns its modules, a CODEOWNERS file puts that ownership in writing, and changes across modules need approval from both teams. Then, once the need for independent releases becomes measurable, a few coarse-grained services that line up with team boundaries; that is, the service-based corner of the map from the previous post. If service boundaries line up with team boundaries, Conway works in the same direction as you.
Both architectures are right for their time. The mistake is applying one's answer at the other's time: building microservices with 3 people, or keeping 30 people in a single monolith without modules.
Common Pitfalls
Moving to microservices without changing the team structure. Layer teams split microservices by layer too: a "frontend service", a "business service", a "data service". The result is the distributed monolith from the earlier posts.
Leaving ownership unclear. "Everyone develops this module" in practice means "nobody owns it". Every place left without an owner, such as the migrations folder, shared libraries and build scripts, is the first to break when the team structure changes. Every file should have an owner; CODEOWNERS makes that written and checkable.
Treating Team Topologies as relabeling. Renaming the existing backend team to "platform team" changes nothing. A platform team's product is a platform only if it really makes the work of the teams using it easier.
Making collaboration mode permanent. Two teams always working together is a sign that they should really be one team, or that the boundary between them is in the wrong place.
Treating the Inverse Conway Maneuver as a one-off reorganization. Changing the organization overnight and expecting the architecture to follow the next day produces exactly the friction Fowler warns about. Team changes should move in small steps, together with restructuring the code.
A Small Experiment
★☆☆ Look at the last 100 commits in your own repository with git log --name-only and count how many different top-level folders each commit touches. Which two folders change together most often? Do those two folders belong to the same team?
★★☆ Write an ownership file that adapts the tool in this post to your own repository and team structure. List the files that match no rule and come out as (unowned). Who should own them?
★★★ Extend the tool: group commits into the last 30, 90 and 180 days and print how the cross-team commit ratio changes over time. If the ratio is rising, is the team structure or the architecture falling behind? Also list the files two teams touch together most often; they are the first sign that a boundary is in the wrong place.
In this post we measured how architecture is shaped by team structure. But even if you start with the right team structure and the right architecture, the code drifts away from the decisions over time: a shortcut, an urgent report, a "for now" reference. In the next and last post of the series we look at that drift, architecture erosion, and protect the decisions automatically in CI by turning them into ArchUnitNET tests and turning query count into a fitness function.