Software looks flat.
We see files.
Folders.
Functions.
Classes.
Endpoints.
Variables.
Tables.
Queues.
Processes.
Threads.
Services.
Repositories.
At first glance, software appears to be nothing more than text arranged into directories.
But that is an illusion.
Underneath every sufficiently complex software system is a geometry.
Not geometry in the traditional sense of triangles, circles, angles, and physical dimensions.
I mean a computational geometry.
A geometry of relationships.
A geometry of distance.
A geometry of paths.
A geometry of boundaries.
A geometry of states.
A geometry of transformations.
A geometry of dependencies.
A geometry of information flow.
And once software becomes large enough, understanding this hidden geometry becomes more important than understanding individual lines of code.
A function might be only twenty lines long.
But those twenty lines can sit at the intersection of fifty other components.
A database table might contain only ten columns.
But those columns may represent a huge portion of the application's state space.
An API endpoint might look like:
POST /orders
Yet conceptually it might represent a transformation through authentication, authorization, validation, inventory reservation, payment, persistence, messaging, caching, and eventual consistency.
The endpoint is not a point.
It is a path.
And this is where software begins to resemble mathematics.
1. Software Is Not a Collection of Files
One of the earliest mistakes developers make is thinking about software spatially in terms of its source tree.
Imagine:
src/
├── controllers/
├── services/
├── models/
├── repositories/
├── middleware/
└── utils/
We tend to think:
The architecture is represented by these folders.
It isn't.
The folders are merely one projection of the architecture.
The actual architecture looks more like this:
┌──────────────┐
│ Client │
└──────┬───────┘
│
▼
┌──────────────┐
│ API Gateway │
└──────┬───────┘
│
┌──────────┴──────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Auth Service│ │Order Service│
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ User Store │ │ Order Store │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ Event Bus │
└──────┬──────┘
│
┌─────────┴─────────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ Analytics │ │ Notification│
└────────────┘ └────────────┘
This is a graph.
The files are not the system.
The relationships between the files are the system.
That distinction becomes increasingly important as software grows.
A thousand files with weak relationships can be easier to understand than fifty files with chaotic relationships.
This gives us our first principle:
Software complexity is often more closely related to relationship density than source-code volume.
A program with 10,000 lines can be simple.
A program with 2,000 lines can be terrifying.
The difference is often geometry.
2. The First Geometry: Graphs
Graph theory is hiding everywhere in computer science.
A graph consists, conceptually, of:
$$
G = (V,E)
$$
where:
- (V) represents vertices
- (E) represents edges
In software:
V = components
E = relationships
For example:
User
│
├────► Authentication
│
├────► Orders
│
└────► Payments
Orders
│
├────► Inventory
│
├────► Payments
│
└────► Notifications
We can represent this as:
$$
G = (V,E)
$$
where:
V = {
User,
Auth,
Orders,
Payments,
Inventory,
Notifications
}
and:
E = {
User → Auth,
User → Orders,
Orders → Inventory,
Orders → Payments,
Orders → Notifications
}
Suddenly architecture becomes mathematical.
We can calculate:
- degree
- connectivity
- centrality
- reachability
- cycles
- paths
- bottlenecks
- clusters
This is not academic decoration.
These properties describe real software behavior.
3. Dependency Graphs Are Software Geometry
Consider:
from payment import PaymentService
from inventory import InventoryService
from notification import NotificationService
This creates relationships.
The order service depends on three other components.
We could represent it as:
Payment
▲
│
│
Inventory ◄── Order ──► Notification
The Order component has a high degree.
That makes it structurally important.
But high degree is not automatically bad.
A router naturally has high degree.
An API gateway naturally has high degree.
The problem occurs when a component becomes a dependency supernode.
For example:
┌────────────┐
│ Utils │
└─────┬──────┘
▲
┌─────────────┼──────────────┐
│ │ │
│ │ │
Orders Users Payments
▲ ▲ ▲
│ │ │
└─────────────┼──────────────┘
│
Utils
Now imagine utils.py contains:
- database logic
- authentication
- HTTP requests
- business rules
- formatting
- caching
- environment configuration
It has become a geometric singularity.
Everything depends on it.
Changing one supposedly harmless helper can cause damage across the entire system.
This is why some software systems feel "fragile."
Their geometry is fragile.
4. Coupling Is Distance
One of the most useful ways to think about coupling is as a form of distance.
Suppose:
A → B → C → D
A change in A may propagate through B and C before affecting D.
The conceptual dependency distance is:
$$
d(A,D)=3
$$
Now consider:
A ───────────────► D
The distance is:
$$
d(A,D)=1
$$
But there is a catch.
Shorter isn't always better.
A direct dependency can actually increase coupling.
Consider:
Order Service ─────────► PostgreSQL
versus:
Order Service
│
▼
Repository Interface
│
▼
PostgreSQL
The second system has a longer path but potentially better architectural separation.
So software distance isn't merely the number of edges.
It is semantic distance.
Two components can be physically close in a dependency graph while being conceptually very far apart.
5. Abstraction Creates Space
Abstraction is one of the most misunderstood concepts in programming.
People often say:
Abstraction hides complexity.
That's true, but incomplete.
Abstraction creates a new coordinate system.
Suppose we have:
def save_user(user):
db.execute(
"INSERT INTO users (...) VALUES (...)",
...
)
We can abstract persistence:
class UserRepository:
def save(self, user):
...
Now the rest of the application no longer operates directly in database space.
It operates in repository space.
The abstraction creates a boundary:
APPLICATION SPACE
─────────────────────────────────────────
UserRepository
─────────────────────────────────────────
DATABASE SPACE
PostgreSQL / SQLite
─────────────────────────────────────────
The application doesn't need to know every coordinate of the database.
It interacts through a projection.
This is similar to mathematics.
You don't always operate directly on raw coordinates.
You choose a representation appropriate to the problem.
Good abstractions do exactly this.
They change the geometry in which developers reason.
6. APIs Are Coordinate Systems
Consider an API:
GET /users/42
At the HTTP level, this is a simple request.
But internally:
HTTP Request
│
▼
Router
│
▼
Authentication
│
▼
Authorization
│
▼
Validation
│
▼
Controller
│
▼
Service
│
▼
Repository
│
▼
Database
The API endpoint compresses this entire path into a single interface.
It is effectively a coordinate system for accessing application state.
The client says:
I want the resource represented by
/users/42.
The server translates that coordinate into a computational path.
This gives us a powerful way to think about APIs:
An API is a coordinate system over a system's capabilities.
REST does this using resources.
RPC does this using procedures.
GraphQL does this using a query graph.
Event-driven architectures do this using events.
Different architectures provide different coordinate systems over computational space.
7. State Space: The Geometry of Possibility
Software isn't just a graph of components.
It is also a space of possible states.
Consider a login system.
A user might be:
Unauthenticated
│
▼
Credentials Submitted
│
▼
Authenticating
│
├──── invalid ────► Failed
│
▼
Authenticated
│
▼
Session Expired
│
▼
Unauthenticated
This is a state machine.
Mathematically:
$$
S = {s_1,s_2,\ldots,s_n}
$$
and transitions:
$$
T:S\times A\rightarrow S
$$
where (A) represents possible actions.
For example:
State:
Authenticated
Action:
logout
Result:
Unauthenticated
The software is moving through a state space.
8. Bugs Are Often Illegal Regions of State Space
This idea gets interesting.
Suppose an order can be:
Pending
Paid
Shipped
Delivered
Cancelled
But your application allows:
Delivered → Pending
That's probably nonsense.
The state exists syntactically but not semantically.
We can visualize the valid state graph:
Pending
│
├──────► Cancelled
│
▼
Paid
│
▼
Shipped
│
▼
Delivered
Now imagine:
Delivered ─────► Pending
You've introduced an illegal edge.
This is why state-machine design is so powerful.
It lets us define the geometry of valid behavior.
Instead of asking:
Does this code work?
we ask:
Does this transition exist in the valid state space?
That's a much stronger question.
9. Implementation: A Safe State Machine
Here's a simple implementation in Python:
from enum import Enum
class OrderState(Enum):
PENDING = "pending"
PAID = "paid"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
TRANSITIONS = {
OrderState.PENDING: {
OrderState.PAID,
OrderState.CANCELLED,
},
OrderState.PAID: {
OrderState.SHIPPED,
},
OrderState.SHIPPED: {
OrderState.DELIVERED,
},
OrderState.DELIVERED: set(),
OrderState.CANCELLED: set(),
}
def transition(current, target):
if target not in TRANSITIONS[current]:
raise ValueError(
f"Illegal transition: {current.value} -> {target.value}"
)
return target
Now:
state = OrderState.PENDING
state = transition(
state,
OrderState.PAID
)
state = transition(
state,
OrderState.SHIPPED
)
state = transition(
state,
OrderState.DELIVERED
)
But:
transition(
OrderState.DELIVERED,
OrderState.PENDING
)
fails.
The program isn't merely checking values.
It is enforcing the geometry of valid states.
10. Type Systems Define Boundaries
A type system is another geometric mechanism.
Consider:
fn transfer(
from: Account,
to: Account,
amount: Money
)
This function doesn't accept arbitrary things.
You can't pass:
String
File
Socket
Image
unless those types satisfy the required contract.
Types create boundaries around valid values.
Conceptually:
ALL POSSIBLE VALUES
──────────────────────────────────────────
┌─────────────────────────┐
│ Money │
│ │
│ valid monetary values │
└─────────────────────────┘
The type system says:
Only points inside this region are valid inputs.
This is one reason strongly typed systems can eliminate entire categories of bugs.
They reduce the reachable state space.
11. Types Are Constraints on Geometry
Suppose a function accepts:
def divide(a, b):
return a / b
Its mathematical domain excludes:
$$
b=0
$$
But ordinary runtime types don't express this.
A more precise conceptual type would be:
$$
b \in \mathbb{R}\setminus{0}
$$
That is a geometric restriction.
We are defining the valid domain.
Programming languages increasingly allow developers to encode more of these constraints.
For example:
struct NonZero(i32);
impl NonZero {
fn new(value: i32) -> Option<Self> {
if value == 0 {
None
} else {
Some(Self(value))
}
}
}
Now:
i32
│
├── 0
│
└── non-zero values
│
▼
NonZero
The constructor acts like a gate into a smaller valid region.
This is what good type design does.
12. Data Has Geometry Too
Consider a database schema:
users
────────────────────
id
name
email
created_at
and:
orders
────────────────────
id
user_id
amount
status
created_at
There is a relationship:
User
│
│ 1:N
▼
Order
The relational database is fundamentally geometric.
Tables are sets.
Relationships connect sets.
Indexes create navigational structures.
Foreign keys create constraints.
Queries traverse relationships.
A SQL query such as:
SELECT users.name, orders.amount
FROM users
JOIN orders
ON users.id = orders.user_id;
is essentially asking the database to traverse a graph.
The SQL hides the graph traversal.
But the graph still exists.
13. Query Optimization Is Geometry
Database optimization becomes fascinating when viewed geometrically.
Suppose:
Users
│
▼
Orders
│
▼
Payments
A naive query might scan everything.
An optimized query tries to reduce the amount of space traversed.
An index changes the geometry of access.
Without an index:
[1][2][3][4][5][6][7][8][9][10]
↑
scan
With a B-tree index:
[5]
/ \
[2] [8]
/ \ / \
1 3 6 9
Instead of traversing the entire collection, the database follows a structured path.
This is geometry converted into performance.
14. Caches Change the Shape of Computation
Suppose a function normally performs:
Request
│
▼
API
│
▼
Database
│
▼
Result
Add caching:
┌──────────┐
│ Cache │
└────┬─────┘
▲
│
Request ─────► API
│
▼
Database
Now there are two possible paths.
Cache hit:
$$
Request \rightarrow Cache \rightarrow Response
$$
Cache miss:
$$
Request \rightarrow Cache \rightarrow Database \rightarrow Cache \rightarrow Response
$$
The system's geometry has changed.
Caching is therefore not merely an optimization.
It introduces:
- new states
- new paths
- new consistency problems
- new failure modes
- new timing relationships
This is why distributed caches can make systems harder to reason about.
You have added dimensions.
15. Time Is a Dimension of Software
Traditional diagrams often show space.
But distributed systems require another dimension:
time.
Consider two services:
Service A Service B
│ │
│──── event ────────────────►│
│ │
│ │
│ │
│◄──── response ─────────────│
The meaning of the system depends not only on what happened, but when it happened.
Imagine:
t0: Payment created
t1: Payment processed
t2: Order shipped
t3: Payment reversed
Now the system has entered a strange state:
Order = Shipped
Payment = Reversed
This is a temporal inconsistency.
Distributed systems are difficult partly because the geometry isn't just:
$$
G=(V,E)
$$
It is closer to:
$$
G(t)
$$
A graph that changes over time.
16. Concurrency Creates Geometric Problems
Suppose two requests arrive simultaneously:
Request A ─────► Read balance = $100
Request B ─────► Read balance = $100
Request A ─────► Withdraw $80
Request B ─────► Withdraw $80
If both operate on stale state:
Final balance = $20
when mathematically:
$$
100 - 80 - 80 = -60
$$
The system has allowed two paths through state space that should have been mutually exclusive.
Concurrency control is therefore partly about controlling which paths are allowed to intersect.
Locks do this.
Transactions do this.
Atomic operations do this.
Compare-and-swap does this.
Distributed consensus does this at a much larger scale.
17. Locks Create Exclusion Zones
Imagine a shared resource:
Resource
│
┌─────┴─────┐
│ │
Thread A Thread B
Without synchronization:
A ─────► read
B ─────► read
A ─────► write
B ─────► write
The paths overlap.
With a lock:
A ─────► LOCK ─────► READ ─────► WRITE ─────► UNLOCK
│
▼
B ───────────────────────────────────────────────► LOCK
The lock creates an exclusion region.
Only one execution path may occupy the critical section at a time.
18. Distributed Systems Are Geometry Without a Shared Coordinate System
This may be one of the deepest ideas in distributed computing.
A single-threaded program has a relatively clear notion of sequence:
A → B → C → D
A distributed system has:
Node A: A1 → A2 → A3
Node B: B1 → B2 → B3
Node C: C1 → C2 → C3
There is no universal clock that magically makes everything globally ordered.
Messages travel.
Packets are delayed.
Nodes fail.
Clocks drift.
Events arrive out of order.
So we have a problem:
How do we reason about geometry when different observers have different coordinate systems?
Distributed systems solve this with mechanisms such as:
- logical clocks
- vector clocks
- consensus
- sequence numbers
- epochs
- transaction IDs
- causal metadata
Lamport clocks are a beautiful example.
Instead of trying to perfectly know physical time, we construct an ordering relation.
If:
$$
A \rightarrow B
$$
then we can establish that A happened before B.
The system creates a partial order.
19. Software Geometry Is Often Partial
This is another important idea.
Developers often imagine software as deterministic:
input → function → output
But large systems are often closer to:
┌──► Success
Input ──────────┼──► Retry
├──► Timeout
├──► Partial failure
├──► Duplicate
└──► Unknown
The output isn't always a single point.
It's a region of possibilities.
This is why distributed APIs need concepts such as:
- idempotency
- retries
- timeouts
- circuit breakers
- deduplication
- transactional boundaries
You are not merely implementing the happy path.
You are defining the shape of the failure space.
20. Exceptions Are Paths Through Failure Geometry
Consider:
try:
result = payment()
except TimeoutError:
retry()
except NetworkError:
queue_for_later()
except PermissionError:
reject()
This isn't just error handling.
It is branching through a failure graph.
Payment
│
┌──────────┼──────────┐
▼ ▼ ▼
Success Timeout Network
│ │
▼ ▼
Retry Queue
Different failures lead to different regions of behavior.
A mature system explicitly models these paths.
An immature system accidentally discovers them in production.
21. Microservices Increase Dimensionality
Microservices are often presented as:
Split your application into smaller services.
That's incomplete.
You are also increasing the number of boundaries.
Monolith:
┌──────────────────────────────┐
│ │
│ Users Orders Payments │
│ Inventory Notifications │
│ │
└──────────────────────────────┘
Microservices:
┌────────┐ ┌────────┐
│ Users │────►│ Orders │
└────────┘ └───┬────┘
│
┌────┴─────┐
▼ ▼
┌─────────┐ ┌──────────┐
│Payments │ │Inventory │
└─────────┘ └──────────┘
Now every boundary can introduce:
- latency
- serialization
- failure
- retries
- authentication
- versioning
- observability requirements
Microservices don't remove complexity.
They redistribute it into geometry.
22. Network Calls Are Long Edges
A function call:
result = calculate()
may cost nanoseconds or microseconds.
A network call:
result = requests.get(...)
can involve:
Application
│
▼
Kernel
│
▼
Network Interface
│
▼
Router
│
▼
Internet
│
▼
Load Balancer
│
▼
Server
│
▼
Database
That is a very long edge.
Therefore:
A network call should be treated as a geometric boundary, not merely another function call.
This explains why distributed architecture requires different reasoning from local architecture.
23. Zero-Copy Is About Removing Geometric Movement
Consider data flowing through memory:
Disk
│
▼
Kernel Buffer
│
▼
Application Buffer
│
▼
Network Buffer
│
▼
Socket
Every copy is movement through memory space.
Zero-copy techniques try to reduce unnecessary movement:
Disk ─────────────────────────────► Network
Conceptually, the data travels through fewer regions.
This is why zero-copy systems can be extremely fast.
The optimization isn't only:
Make the CPU work faster.
It is:
Reduce the distance data has to travel.
24. Pipelines Are Paths
Consider a compiler:
Source Code
│
▼
Lexer
│
▼
Parser
│
▼
AST
│
▼
Semantic Analysis
│
▼
IR
│
▼
Optimizer
│
▼
Machine Code
This is a transformation pipeline.
Each stage maps one representation into another.
Mathematically:
$$
f_1:X_0\rightarrow X_1
$$
$$
f_2:X_1\rightarrow X_2
$$
$$
f_3:X_2\rightarrow X_3
$$
Therefore:
$$
F = f_3\circ f_2\circ f_1
$$
The compiler is a composition of transformations.
And this pattern exists everywhere.
25. Functional Programming Makes the Geometry Explicit
Suppose:
result = f(g(h(x)))
We can visualize:
x
│
▼
h
│
▼
g
│
▼
f
│
▼
result
Each function transforms the point from one space into another.
For example:
$$
h:X\rightarrow Y
$$
$$
g:Y\rightarrow Z
$$
$$
f:Z\rightarrow W
$$
Composition creates:
$$
f\circ g\circ h:X\rightarrow W
$$
This is one reason functional programming can feel mathematically elegant.
It makes transformations explicit.
26. Serialization Is a Coordinate Transformation
Suppose we have an in-memory object:
user = {
"id": 42,
"name": "Derek"
}
Convert it to JSON:
{
"id": 42,
"name": "Derek"
}
The information hasn't necessarily changed.
Its representation has.
Conceptually:
Memory Representation
│
▼
Serializer
│
▼
Wire Representation
Then:
Wire Representation
│
▼
Deserializer
│
▼
Memory Representation
Serialization is therefore another geometric transformation between representational spaces.
27. Event-Driven Systems Form Graphs of Causality
Imagine:
OrderCreated
│
├────► ReserveInventory
│
├────► SendConfirmation
│
└────► UpdateAnalytics
One event produces multiple paths.
This is a branching graph.
Later:
ReserveInventory
│
▼
InventoryReserved
│
├────► ShipOrder
└────► UpdateStock
Now we have a causal graph.
This is extremely useful for debugging.
When something goes wrong, you can ask:
What path produced this state?
Instead of searching linearly through source code, you traverse the causality graph.
28. Observability Is Mapping the Hidden Geometry
Logs are points.
Metrics are measurements.
Traces are paths.
This is an interesting way to think about observability.
A distributed trace:
Request
│
├──► Auth
│
├──► Orders
│ │
│ ├──► DB
│ └──► Payment
│
└──► Notifications
is effectively reconstructing the runtime path through the architecture.
Without tracing, the architecture is mostly static.
With tracing, you can observe its dynamic geometry.
A trace can answer:
Where did the request go?
How long did it spend there?
Where did it fail?
What did it call?
What caused the latency?
Observability is therefore not just monitoring.
It is measurement of runtime geometry.
29. Latency Is Distance Measured in Time
Suppose:
Client
│
▼
API Gateway 5 ms
│
▼
Auth 10 ms
│
▼
Orders 20 ms
│
▼
Database 50 ms
Total:
$$
L = 5 + 10 + 20 + 50
$$
$$
L = 85ms
$$
But real distributed systems are rarely purely sequential.
Consider parallel execution:
┌──► Auth ──────── 10ms
Request ──────┤
└──► Profile ───── 40ms
If they execute concurrently:
$$
L \approx \max(10,40)=40ms
$$
not:
$$
10+40=50ms
$$
The shape of the execution graph determines latency.
This is a profound performance principle:
Performance is often a property of topology.
Not merely CPU speed.
30. Critical Paths
In a dependency graph, some paths determine the minimum completion time.
Consider:
┌── Service A: 20ms
Request ─────┤
├── Service B: 80ms
│
└── Service C: 30ms
If these run in parallel:
$$
T=80ms
$$
Service B lies on the critical path.
Optimizing Service A from 20ms to 5ms changes almost nothing.
Optimizing Service B from 80ms to 40ms changes everything.
This is why blindly optimizing the "slowest function" isn't enough.
You need to understand the geometry of execution.
31. Architecture Is About Controlling Shape
A good architecture doesn't eliminate complexity.
It shapes it.
Compare two systems.
System A
A ─► B ─► C ─► D
│ │ │ │
└────┴────┴────┘
Many cross-connections.
System B
API
/ | \
▼ ▼ ▼
A B C
│
▼
D
The second system has clearer boundaries.
A change in A is less likely to affect D.
The architecture has constrained the propagation paths.
This is what modularity really means.
It isn't simply:
Put things in different files.
It means:
Control the topology of change.
32. The Geometry of Change
This may be the most practical application of the entire idea.
Suppose you change:
User.email
How far does that change propagate?
Maybe:
Database
│
▼
ORM Model
│
▼
Repository
│
▼
Service
│
▼
API Serializer
│
▼
Frontend
│
▼
Mobile App
That is a change-propagation path.
A well-designed system limits this radius.
A poorly designed system lets changes ripple everywhere.
We can think of:
$$
R(c)
$$
as the change radius of component (c).
The smaller the radius, the more localized the change.
Good architecture attempts to minimize unnecessary change radius.
33. Dependency Inversion Changes the Geometry
Suppose:
Business Logic ─────► PostgreSQL
The business logic is geometrically attached to the database.
Introduce an interface:
Business Logic
│
▼
Repository Interface
▲
│
PostgreSQL Adapter
Now the dependency direction changes.
The business logic depends on an abstraction.
The database becomes an implementation detail.
This creates a more stable center:
┌──────────────┐
│ Domain Rules │
└───────┬──────┘
│
┌────────▼────────┐
│ Interfaces │
└────────┬────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
SQLite PostgreSQL API
The architecture has changed its topology.
That is the real power of dependency inversion.
34. Implementation: A Geometrically Decoupled Service
Consider:
from abc import ABC, abstractmethod
class UserRepository(ABC):
@abstractmethod
def find(self, user_id):
pass
class UserService:
def __init__(self, repository):
self.repository = repository
def get_user(self, user_id):
return self.repository.find(user_id)
Now the service doesn't know whether the repository uses:
PostgreSQL
SQLite
MongoDB
Redis
HTTP
Memory
We can implement:
class InMemoryUserRepository(UserRepository):
def __init__(self):
self.users = {
1: {"id": 1, "name": "Derek"}
}
def find(self, user_id):
return self.users.get(user_id)
And:
repository = InMemoryUserRepository()
service = UserService(repository)
print(service.get_user(1))
The geometry becomes:
UserService
│
▼
UserRepository
▲ ▲
│ │
PostgreSQL Memory
The center is stable.
The edges are replaceable.
35. Security Is Boundary Geometry
Security is often described as:
Authentication + authorization + encryption.
But geometrically, security is about controlling boundaries.
Imagine:
Internet
│
▼
┌──────────────┐
│ Public API │
└──────┬───────┘
│
▼
┌──────────────┐
│ Auth Layer │
└──────┬───────┘
│
▼
┌──────────────┐
│ Business │
└──────┬───────┘
│
▼
┌──────────────┐
│ Database │
└──────────────┘
Each boundary is a security checkpoint.
Zero-trust architecture takes this further.
Instead of assuming:
Inside = trusted
Outside = untrusted
we assume:
Every boundary requires verification.
The geometry becomes fragmented intentionally.
Security is partly the art of deciding:
Which paths are allowed to cross which boundaries?
36. Permissions Form a Graph
Consider:
User
│
├── Role: Admin
│ │
│ ├── read
│ ├── write
│ └── delete
│
└── Organization
│
└── Project
Authorization can become a graph traversal problem.
For example:
User
↓
Organization
↓
Project
↓
Resource
↓
Permission
To determine whether a user can modify a resource, we traverse the relationship graph.
This is why authorization systems become difficult at scale.
The question isn't simply:
if user.is_admin:
It can become:
Is there a valid path from this identity to this resource containing the required permission under the current policy?
That's graph theory wearing a security badge.
37. Software Has Topology
Topology studies properties that remain meaningful even when exact measurements change.
That idea is surprisingly relevant to software.
Imagine changing:
PostgreSQL → MySQL
The implementation changes.
But perhaps the architectural topology remains:
Service → Repository → Database
Or:
React → API → Service → Database
The exact implementation changed.
The structural relationships did not.
This is why architectural patterns survive technology changes.
You can replace:
Django
with:
Laravel
or:
Node.js
without necessarily changing the fundamental geometry.
Frameworks are often coordinate choices.
Architecture is deeper.
38. Refactoring Is Geometry Editing
When you refactor code, you are often changing its geometry.
Suppose:
A → B
A → C
A → D
A → E
You might introduce:
A → Facade → B
C
D
E
Now A has fewer direct relationships.
Or perhaps you detect:
A → B
B → C
C → A
A cycle.
You might refactor to:
A → B → C
The code may still produce exactly the same outputs.
But its topology has improved.
This is why refactoring can make software easier to reason about without changing visible behavior.
You are editing the geometry while preserving the external projection.
39. Circular Dependencies Are Loops
Consider:
A ─► B
▲ │
│ ▼
D ◄──C
Eventually:
$$
A\rightarrow B\rightarrow C\rightarrow D\rightarrow A
$$
Cycles are not always bad.
Graphs naturally contain cycles.
But architectural cycles can make reasoning difficult.
If changing A requires understanding B, C, and D, and changing D requires understanding A, you have created a strongly coupled region.
Sometimes the correct response is not:
Remove all cycles.
Instead:
Identify whether the cycle represents a legitimate domain relationship or accidental coupling.
Geometry gives us the vocabulary to make that distinction.
40. Software Complexity Is Curvature
Here's a metaphor I find useful.
Imagine two roads.
Road A:
──────────────────────────────
Road B:
──────╮
│
╰──────╮
│
╰────────
Both may connect the same starting and ending points.
But Road B requires more navigation.
Software can be similar.
A function might have simple input/output behavior but be surrounded by:
- hidden state
- callbacks
- global variables
- network requests
- database mutations
- implicit dependencies
- retries
- concurrency
- side effects
The logical path bends.
Developers experience this as cognitive complexity.
We could metaphorically call this software curvature.
The more indirect the reasoning path, the harder the system becomes to navigate mentally.
41. Cognitive Distance Matters
Imagine this:
user = service.create_user(data)
Beautiful.
Now imagine:
user = service.create_user(data)
actually does:
validate
↓
normalize
↓
check cache
↓
call remote service
↓
write database
↓
publish event
↓
invalidate cache
↓
send email
↓
schedule job
The syntax suggests a short path.
The runtime geometry is enormous.
This mismatch creates bugs.
A useful engineering principle emerges:
The simpler an abstraction looks, the more important it is that its hidden geometry remains predictable.
Abstraction should compress complexity.
It should not disguise chaos.
42. Good APIs Compress Geometry
A good API might expose:
POST /orders
while internally performing twenty operations.
That is acceptable if the contract remains predictable.
A bad API might expose:
POST /orders
but randomly trigger unrelated side effects depending on hidden state.
Then the interface is lying about the geometry underneath it.
Good API design therefore means carefully choosing which dimensions to expose.
You don't expose every internal coordinate.
You expose the coordinates users actually need.
43. Eventual Consistency Is Moving Geometry
Consider:
Primary Database
│
▼
Replica
│
▼
Analytics
At time (t_0):
Primary = 100
Replica = 100
At (t_1):
Primary = 120
Replica = 100
At (t_2):
Primary = 120
Replica = 120
The system temporarily occupies different states in different regions.
The architecture isn't globally static.
It is moving toward convergence.
This is the geometry of eventual consistency.
The goal isn't:
Every point has the same value immediately.
It is:
The system's regions eventually converge toward a compatible state.
44. CRDTs Are Algebraic Geometry for Distributed State
Conflict-free replicated data types take this idea even further.
Suppose multiple nodes independently update a replicated data structure.
Instead of requiring a single global sequence, the data type is designed so that certain operations can merge safely.
Conceptually:
Replica A
/ \
/ \
A1 A2
\ /
\ /
Merge
│
▼
Consistent State
The merge operation has useful mathematical properties.
For example, if:
$$
merge(a,b)=merge(b,a)
$$
then the operation is commutative.
If:
$$
merge(merge(a,b),c)
merge(a,merge(b,c))
$$
then it is associative.
And if:
$$
merge(a,a)=a
$$
it is idempotent.
These aren't just elegant equations.
They are engineering tools for building distributed systems that tolerate reordering and duplication.
45. The Hidden Geometry of Git
Even Git is geometric.
A commit graph looks like:
A
│
B
│
C──────D
│ \
E F
\ /
─────
Branches are paths through the commit graph.
Merging combines histories.
Rebasing changes the shape of the visible history.
A commit isn't merely a file snapshot.
It is a node connected to previous nodes.
Git is essentially a graph database specialized for source history.
This is why Git operations can be understood much more easily once you stop imagining branches as physical lines and start imagining commits as graph nodes.
46. Versioning Is Navigating a Space of Representations
Consider API versions:
v1
│
▼
v2
│
▼
v3
But real systems often look like:
┌──► v2
v1 ──────┤
└──► v3
Or:
Client A ──► v1
Client B ──► v2
Client C ──► v3
The system exists in multiple representational regions simultaneously.
Backward compatibility means maintaining valid paths between these regions.
A breaking change removes an edge.
A migration adds an edge.
An adapter creates a bridge.
47. Compilers Reveal the Deepest Geometry
If you want to see software geometry in its purest form, study compilers.
Source code:
for x in items:
total += x
becomes:
Tokens
│
▼
AST
│
▼
Control Flow Graph
│
▼
SSA / IR
│
▼
Optimized IR
│
▼
Machine Instructions
The compiler repeatedly changes the representation while preserving meaning.
This is essentially geometry under transformation.
The original program and the machine code look nothing alike.
But there is a semantic relationship between them.
The compiler constructs a path:
$$
Source
\rightarrow
AST
\rightarrow
IR
\rightarrow
MachineCode
$$
and attempts to preserve semantics across each transformation.
48. Control Flow Graphs Are Literally Graphs
Consider:
if balance > 0:
withdraw()
else:
reject()
The control flow graph is:
Start
│
▼
balance > 0?
/ \
yes no
│ │
▼ ▼
withdraw reject
\ /
\ /
▼ ▼
End
Compilers use this structure to reason about:
- reachability
- dead code
- loops
- optimization
- branching
- execution paths
A developer can use the same mental model.
If a piece of code has too many branches, the graph becomes difficult to traverse.
That's why deeply nested conditionals feel mentally expensive.
49. Complexity Is the Number of Paths You Must Consider
Consider:
if A:
if B:
...
else:
...
else:
if C:
...
else:
...
The number of possible execution paths grows.
With many independent boolean decisions, the theoretical space can approach:
$$
2^n
$$
for (n) binary conditions.
For:
$$
n=10
$$
we have:
$$
2^{10}=1024
$$
potential combinations.
For:
$$
n=20
$$
we have:
$$
2^{20}=1,048,576
$$
Now imagine a production service with dozens of flags, permissions, retries, states, and timing conditions.
This is why software can become impossible to test exhaustively.
The geometry of possibilities explodes.
50. Testing Is Sampling the State Space
You cannot usually test every possible state.
So testing becomes a sampling problem.
Imagine:
State Space
┌────────────────────────┐
│ . . │
│ . . │
│ . X │
│ . │
│ . . │
└────────────────────────┘
Tests sample points.
Property-based testing takes this idea seriously.
Instead of writing only:
assert add(2, 3) == 5
you might test a property:
$$
a+b=b+a
$$
for many generated values.
Now you are testing a region of behavior rather than a single point.
51. Fuzzing Explores Weird Regions
Fuzzing is essentially automated exploration of strange regions in input space.
Instead of:
normal input
you get:
empty
huge
negative
malformed
random
nested
unicode
binary
truncated
unexpected
The goal is to find regions where the program's assumptions break.
Security researchers use this constantly.
The vulnerability often isn't in the normal region.
It's near the boundary.
52. Bugs Live Near Boundaries
This is a useful heuristic.
Consider:
Valid Input
──────────────────────────
↑
boundary
↓
──────────────────────────
Invalid Input
Many bugs occur near:
- zero
- empty collections
- maximum integer values
- minimum values
- time boundaries
- permission boundaries
- transaction boundaries
- network failures
- concurrent updates
For example:
if age >= 18:
What happens at exactly:
age = 18
Boundary conditions matter because they are where the system changes regions.
53. Rate Limiting Is Geometric Capacity Control
Suppose an API allows:
$$
100
$$
requests per minute.
The system defines an allowed region:
Requests
│
100├────────────── limit
│
│
│
0└──────────────── Time
Cross the boundary:
101 requests/minute
and the system transitions into another region:
Allowed → Rate Limited
Algorithms such as token buckets are essentially geometric capacity models.
A token bucket can be visualized as:
┌───────────────┐
│ TOKENS │
│ ● ● ● ● ● ● │
└───────┬───────┘
│
▼
Request
Tokens represent permission to occupy computational capacity.
54. Queues Create Distance Between Production and Consumption
A queue:
Producer
│
▼
┌─────────────────────┐
│ A B C D E F G │
└──────────┬──────────┘
│
▼
Consumer
creates temporal separation.
The producer doesn't need to wait for the consumer.
This transforms:
Producer → Consumer
into:
Producer → Queue → Consumer
The extra node changes the geometry.
But it also creates:
- buffering
- backpressure
- ordering
- retries
- duplicates
- persistence
- latency
Again, the new component doesn't merely "help."
It changes the shape of the system.
55. Backpressure Is Geometry Under Load
Imagine a producer generating:
$$
1000 \text{ messages/sec}
$$
while a consumer handles:
$$
100 \text{ messages/sec}
$$
The queue grows.
If:
$$
P>C
$$
where (P) is production rate and (C) is consumption rate, backlog grows.
Approximate backlog growth:
$$
B(t)=B_0+(P-C)t
$$
For:
$$
P=1000
$$
and:
$$
C=100
$$
we get:
$$
B(t)=B_0+900t
$$
The queue is accumulating geometric distance between production and consumption.
Backpressure mechanisms attempt to control this divergence.
56. Software Performance Is Movement Through Space
When optimizing a system, ask:
Where does data move?
Where does execution move?
Where does control move?
Where does time accumulate?
Where do dependencies branch?
Where do requests cross boundaries?
These questions often reveal performance problems faster than staring at source code.
For example:
Client
↓
Gateway
↓
Service
↓
Service
↓
Service
↓
Database
Maybe the biggest optimization isn't making any service 10% faster.
Maybe it is eliminating two network hops.
That is a geometric optimization.
57. The Best Architecture Often Removes Edges
Developers love adding things:
Add cache.
Add queue.
Add service.
Add abstraction.
Add framework.
Add middleware.
Add proxy.
Add event bus.
But architecture is also subtraction.
Sometimes the best optimization is:
A → B → C
becoming:
A → C
Sometimes the best performance improvement is:
Service → Service → Service
becoming:
Service → Database
Sometimes the best abstraction is deleting an abstraction.
Complexity grows through edges.
Therefore:
A mature engineer doesn't only ask what component should be added. They ask which relationship should disappear.
58. A Practical Architecture Geometry Checklist
When designing a system, inspect five geometries.
1. Dependency geometry
Ask:
Who depends on whom?
Look for:
- cycles
- supernodes
- unnecessary dependencies
- unstable dependencies
2. State geometry
Ask:
What states can exist?
What transitions are valid?
Look for:
- impossible states
- missing transitions
- race conditions
- invalid combinations
3. Data geometry
Ask:
Where does data live?
Where does it move?
How many times is it copied?
Look for:
- unnecessary serialization
- repeated queries
- excessive copies
- inefficient joins
4. Temporal geometry
Ask:
What happens before what?
What can happen concurrently?
What can arrive late?
Look for:
- races
- stale reads
- retries
- timeouts
- eventual consistency
5. Failure geometry
Ask:
What happens when every edge breaks?
Look for:
- cascading failures
- retry storms
- deadlocks
- partial failure
- unavailable dependencies
59. A Small Architecture Analyzer
We can even model software geometry programmatically.
from collections import defaultdict
class DependencyGraph:
def __init__(self):
self.edges = defaultdict(set)
def add_dependency(self, source, target):
self.edges[source].add(target)
def dependencies_of(self, node):
return self.edges[node]
def degree(self, node):
return len(self.edges[node])
def nodes(self):
return set(self.edges.keys()) | {
target
for targets in self.edges.values()
for target in targets
}
Example:
graph = DependencyGraph()
graph.add_dependency("API", "Auth")
graph.add_dependency("API", "Orders")
graph.add_dependency("Orders", "Database")
graph.add_dependency("Orders", "Payments")
graph.add_dependency("Payments", "Database")
Now:
print(graph.dependencies_of("Orders"))
print(graph.degree("Orders"))
We can begin asking structural questions.
Which component has the highest degree?
Which component has the most incoming edges?
Where are the cycles?
Which nodes are disconnected?
Which components sit on critical paths?
You are effectively doing architectural analysis using graph theory.
60. From Source Code to Geometry
Imagine taking an entire repository and transforming it:
SOURCE CODE
│
▼
Parser
│
▼
Imports
│
▼
Dependency Graph
│
▼
Architecture Metrics
You could calculate:
$$
degree(v)
$$
$$
in_degree(v)
$$
$$
out_degree(v)
$$
$$
path_length(a,b)
$$
$$
centrality(v)
$$
$$
cycles(G)
$$
You could even construct a "change radius" metric.
For a changed node (v):
$$
R(v)=|{u : v\leadsto u}|
$$
where (v\leadsto u) means there is a reachable dependency path from (v) to (u).
A large (R(v)) means changes to (v) can potentially propagate widely.
This gives a quantitative way to think about coupling.
61. Architecture Diagrams Are Maps
A good architecture diagram is not decoration.
It is a map.
A map does not reproduce the entire world.
It selects meaningful structure.
A useful software diagram should therefore answer:
Where are the boundaries?
Where does data move?
Where does control move?
Where are the bottlenecks?
Where are the trust boundaries?
Where are the failure boundaries?
A diagram that contains fifty boxes and fifty arrows may technically be accurate.
But if nobody can reason from it, it has failed as a map.
The purpose of abstraction is navigation.
62. The Developer Is a Navigator
This is why experienced developers sometimes seem to "see" architecture differently.
They aren't necessarily memorizing more code.
They're navigating a different representation.
A beginner sees:
service.create_order()
An experienced engineer may mentally see:
HTTP
↓
Auth
↓
Validation
↓
Order Service
↓
Inventory
↓
Transaction
↓
Database
↓
Event
↓
Queue
↓
Notification
They see the hidden path.
This is one of the major differences between understanding code and understanding systems.
63. The Most Dangerous Code Is Code With Invisible Edges
Consider global state:
current_user = ...
A function reads it without declaring the dependency.
The source code suggests:
function()
The actual geometry is:
function()
│
└────► global state
Dependency injection makes the edge visible:
def function(current_user):
...
Now:
current_user ─────► function
Visible dependencies are easier to reason about.
Hidden dependencies create invisible edges.
Invisible edges are architectural debt.
64. Frameworks Are Coordinate Systems
Django.
Laravel.
Rails.
Spring.
React.
Vue.
Express.
FastAPI.
They provide coordinate systems for constructing software.
A framework tells you:
where things go
how components connect
how requests flow
how state is represented
how persistence is accessed
This is why learning a new framework can feel difficult even when the language is familiar.
You aren't learning syntax.
You're learning a new geometry.
Once you recognize the recurring structures, switching frameworks becomes easier.
The names change.
The shapes don't.
65. The Same Shapes Keep Appearing
Across technology, we repeatedly encounter:
Graph
Tree
Pipeline
Queue
State Machine
Layer
Boundary
Ring
Mesh
Hierarchy
Directed Acyclic Graph
Git uses graphs.
Compilers use graphs.
Databases use trees and graphs.
Networks use graphs.
Operating systems use queues and state machines.
APIs use layered boundaries.
Distributed systems use causal graphs.
Build systems use dependency DAGs.
Package managers solve graph problems.
Machine learning uses high-dimensional spaces.
Software engineering is full of geometry.
We just don't always call it that.
66. Machine Learning Makes the Geometry Literal
In machine learning, data is explicitly represented as points in high-dimensional spaces.
An embedding might map an object:
$$
x\rightarrow\mathbb{R}^{768}
$$
Two semantically similar objects may occupy nearby regions.
Conceptually:
Dog ●
\
● Puppy
● Database
● Cat
Distance becomes meaningful.
For example:
$$
d(x,y)=\sqrt{\sum_i(x_i-y_i)^2}
$$
or cosine similarity:
$$
\cos(\theta)=
\frac{x\cdot y}
{|x||y|}
$$
Here software isn't merely metaphorically geometric.
It is literally operating on mathematical spaces.
67. Vector Databases Are Geometric Databases
A vector database stores points:
x₁
x₂
x₃
...
xₙ
A query becomes:
Find points near this point.
This is fundamentally different from:
WHERE name = 'Derek'
The database is now navigating a high-dimensional space.
Search becomes:
$$
nearest(q,X)
$$
The hidden geometry becomes the primary data structure.
This is a beautiful example of how software architecture changes when the underlying mathematical representation changes.
68. APIs of the Future May Become Geometric Interfaces
Traditional API:
GET /products/42
Graph-oriented API:
query {
product(id: 42) {
name
price
seller {
name
}
}
}
The client isn't simply requesting a point.
It is describing a region of relationships.
The API becomes a navigable graph.
This trend appears in:
- knowledge graphs
- semantic search
- vector databases
- graph databases
- AI agents
- tool-calling systems
Software interfaces are becoming increasingly structural.
69. AI Agents Navigate Software Geometry
Imagine an AI agent with tools:
Agent
/ | \
/ | \
Search API DB
\ | /
\ | /
────┴────
The agent is effectively navigating a tool graph.
The problem becomes:
What tool?
What order?
What state?
What information?
What constraints?
What permissions?
That is a planning problem over a state space.
The agent's intelligence isn't just generating text.
It is finding valid paths through computational geometry.
70. The Future of Software Engineering Is More Mathematical
We often think software engineering is primarily about writing code.
But as systems become larger, engineering increasingly becomes about understanding structures.
Graph theory.
Probability.
Optimization.
Algebra.
Information theory.
Distributed systems.
Formal methods.
Type theory.
These aren't separate academic topics anymore.
They are becoming practical tools for software architecture.
The engineer who understands only syntax can build functions.
The engineer who understands structure can build systems.
71. A New Mental Model
Instead of asking:
What code should I write?
Ask:
What shape should the system have?
Instead of:
What class should I create?
Ask:
What relationship should exist?
Instead of:
How do I fix this bug?
Ask:
What invalid state or path became reachable?
Instead of:
Why is this slow?
Ask:
Where is the computational distance accumulating?
Instead of:
Why is this service fragile?
Ask:
What dependencies are concentrating risk?
Instead of:
Why is this architecture hard to understand?
Ask:
What is the cognitive geometry of the system?
These questions change the quality of engineering decisions.
72. Software Is a Map of Possibilities
At the deepest level, software describes possibilities.
Given:
input
the system determines:
possible states
and:
possible transitions
and:
possible outputs
We can think of a program as:
$$
P:S\times I\rightarrow S\times O
$$
where:
- (S) = state
- (I) = input
- (O) = output
Every program defines a relationship between these spaces.
The source code is merely one representation of that relationship.
73. The Hidden Geometry Is the Real Program
This is the central idea.
The code you read is not necessarily the complete program.
The complete program includes:
Dependencies
+
State
+
Data
+
Control flow
+
Time
+
Concurrency
+
Boundaries
+
Failure paths
+
External systems
Together, these create a structure.
That structure determines what the software can actually do.
A function is only a local piece of the geometry.
An architecture is the global shape.
74. Final Diagram
We can summarize the entire idea like this:
SOFTWARE
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
DEPENDENCY STATE DATA
GRAPH SPACE FLOW
│ │ │
▼ ▼ ▼
PATHS TRANSITIONS MOVEMENT
│ │ │
└──────────────┼──────────────┘
│
▼
TIME
│
▼
CONCURRENCY
│
▼
FAILURE
│
▼
BEHAVIOR
│
▼
SYSTEM SHAPE
The visible source code is only the surface.
Underneath it is a geometry of relationships and possibilities.
75. Conclusion: Code Is the Surface, Geometry Is the System
Software engineers spend enormous amounts of time looking at code.
We should.
Code matters.
But code is only one projection of a deeper structure.
Every import creates an edge.
Every function call creates a path.
Every database relation creates a connection.
Every type creates a boundary.
Every state creates a point in state space.
Every conditional creates a branch.
Every network call creates distance.
Every queue creates temporal separation.
Every cache creates an alternate path.
Every transaction creates a region of atomicity.
Every permission creates a boundary.
Every failure creates another branch.
Every abstraction creates a new coordinate system.
Every architecture is therefore a geometry.
And this gives us a different way to think about software engineering.
When a system is difficult to maintain, don't immediately ask:
Which function is wrong?
Look at the shape.
When a system is slow, don't immediately ask:
Which line is slow?
Look at the paths.
When a system is fragile, don't immediately ask:
Which component is broken?
Look at the edges.
When a system produces impossible behavior, don't only inspect the output.
Inspect the state space.
When a distributed system behaves strangely, inspect its temporal geometry.
When an architecture becomes impossible to understand, inspect the number and direction of its relationships.
When a refactor feels dangerous, ask how far the change can propagate.
When designing a new system, don't begin with frameworks.
Begin with structure.
Draw the graph.
Define the states.
Define the boundaries.
Define the transformations.
Define the paths.
Define the failure regions.
Then choose the technology that best expresses that geometry.
Because ultimately, programming is not merely the art of telling computers what to do.
It is the art of constructing a space in which certain things can happen and other things cannot.
A good architecture doesn't just make correct behavior possible.
It makes incorrect behavior difficult to reach.
A good abstraction doesn't merely hide implementation details.
It gives humans a simpler coordinate system for reasoning.
A good type system doesn't merely describe data.
It shrinks the space of invalid programs.
A good API doesn't expose the entire machine.
It gives users a navigable surface over its capabilities.
A good distributed system doesn't eliminate uncertainty.
It carefully controls the paths through which uncertainty propagates.
And a good engineer isn't simply someone who knows how to write code.
A good engineer learns to see the invisible structure underneath it.
The graphs.
The paths.
The boundaries.
The state spaces.
The transformations.
The dimensions.
The constraints.
The topology.
The geometry.
Once you learn to see that geometry, software stops looking like thousands of unrelated lines.
It starts looking like a landscape.
And suddenly, architecture becomes something you can navigate.
Not because the system became simpler.
But because you finally learned how to see its shape.
The Derek Mwale Principle
Don't just read the code. Read the geometry behind the code.
Because code tells you what exists.
Graphs tell you what connects.
State machines tell you what can happen.
Types tell you what is allowed.
Traces tell you where execution travels.
And architecture tells you how the entire machine fits together.
The most powerful software engineers eventually stop seeing applications as collections of files.
They see them as spaces.
And once you can see the space, you can change the shape.
Top comments (0)