DEV Community

Cover image for The Hidden Geometry of Software
Derek Mwale
Derek Mwale

Posted on

The Hidden Geometry of Software

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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│
                └────────────┘      └────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example:

User
 │
 ├────► Authentication
 │
 ├────► Orders
 │
 └────► Payments

Orders
 │
 ├────► Inventory
 │
 ├────► Payments
 │
 └────► Notifications
Enter fullscreen mode Exit fullscreen mode

We can represent this as:

$$
G = (V,E)
$$

where:

V = {
    User,
    Auth,
    Orders,
    Payments,
    Inventory,
    Notifications
}
Enter fullscreen mode Exit fullscreen mode

and:

E = {
    User → Auth,
    User → Orders,
    Orders → Inventory,
    Orders → Payments,
    Orders → Notifications
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This creates relationships.

The order service depends on three other components.

We could represent it as:

             Payment
                ▲
                │
                │
Inventory ◄── Order ──► Notification
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

versus:

Order Service
      │
      ▼
Repository Interface
      │
      ▼
PostgreSQL
Enter fullscreen mode Exit fullscreen mode

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 (...)",
        ...
    )
Enter fullscreen mode Exit fullscreen mode

We can abstract persistence:

class UserRepository:
    def save(self, user):
        ...
Enter fullscreen mode Exit fullscreen mode

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
─────────────────────────────────────────
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

At the HTTP level, this is a simple request.

But internally:

HTTP Request
     │
     ▼
Router
     │
     ▼
Authentication
     │
     ▼
Authorization
     │
     ▼
Validation
     │
     ▼
Controller
     │
     ▼
Service
     │
     ▼
Repository
     │
     ▼
Database
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But your application allows:

Delivered → Pending
Enter fullscreen mode Exit fullscreen mode

That's probably nonsense.

The state exists syntactically but not semantically.

We can visualize the valid state graph:

Pending
  │
  ├──────► Cancelled
  │
  ▼
Paid
  │
  ▼
Shipped
  │
  ▼
Delivered
Enter fullscreen mode Exit fullscreen mode

Now imagine:

Delivered ─────► Pending
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now:

state = OrderState.PENDING

state = transition(
    state,
    OrderState.PAID
)

state = transition(
    state,
    OrderState.SHIPPED
)

state = transition(
    state,
    OrderState.DELIVERED
)
Enter fullscreen mode Exit fullscreen mode

But:

transition(
    OrderState.DELIVERED,
    OrderState.PENDING
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

This function doesn't accept arbitrary things.

You can't pass:

String
File
Socket
Image
Enter fullscreen mode Exit fullscreen mode

unless those types satisfy the required contract.

Types create boundaries around valid values.

Conceptually:

                 ALL POSSIBLE VALUES
──────────────────────────────────────────
        ┌─────────────────────────┐
        │        Money            │
        │                         │
        │  valid monetary values  │
        └─────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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))
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now:

i32
 │
 ├── 0
 │
 └── non-zero values
        │
        ▼
     NonZero
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and:

orders
────────────────────
id
user_id
amount
status
created_at
Enter fullscreen mode Exit fullscreen mode

There is a relationship:

User
  │
  │ 1:N
  ▼
Order
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

With a B-tree index:

             [5]
           /     \
        [2]       [8]
       /  \      /  \
      1    3    6    9
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add caching:

             ┌──────────┐
             │  Cache   │
             └────┬─────┘
                  ▲
                  │
Request ─────► API
                  │
                  ▼
               Database
Enter fullscreen mode Exit fullscreen mode

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 ─────────────│
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now the system has entered a strange state:

Order = Shipped
Payment = Reversed
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If both operate on stale state:

Final balance = $20
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Without synchronization:

A ─────► read
B ─────► read
A ─────► write
B ─────► write
Enter fullscreen mode Exit fullscreen mode

The paths overlap.

With a lock:

A ─────► LOCK ─────► READ ─────► WRITE ─────► UNLOCK
                                                   │
                                                   ▼
B ───────────────────────────────────────────────► LOCK
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A distributed system has:

Node A:  A1 → A2 → A3

Node B:  B1 → B2 → B3

Node C:  C1 → C2 → C3
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But large systems are often closer to:

                ┌──► Success
Input ──────────┼──► Retry
                ├──► Timeout
                ├──► Partial failure
                ├──► Duplicate
                └──► Unknown
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

This isn't just error handling.

It is branching through a failure graph.

                  Payment
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       Success     Timeout    Network
                     │          │
                     ▼          ▼
                   Retry      Queue
Enter fullscreen mode Exit fullscreen mode

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      │
│                              │
└──────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Microservices:

┌────────┐     ┌────────┐
│ Users  │────►│ Orders │
└────────┘     └───┬────┘
                   │
              ┌────┴─────┐
              ▼          ▼
         ┌─────────┐ ┌──────────┐
         │Payments │ │Inventory │
         └─────────┘ └──────────┘
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

may cost nanoseconds or microseconds.

A network call:

result = requests.get(...)
Enter fullscreen mode Exit fullscreen mode

can involve:

Application
    │
    ▼
Kernel
    │
    ▼
Network Interface
    │
    ▼
Router
    │
    ▼
Internet
    │
    ▼
Load Balancer
    │
    ▼
Server
    │
    ▼
Database
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Every copy is movement through memory space.

Zero-copy techniques try to reduce unnecessary movement:

Disk ─────────────────────────────► Network
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

We can visualize:

x
│
▼
h
│
▼
g
│
▼
f
│
▼
result
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

Convert it to JSON:

{
  "id": 42,
  "name": "Derek"
}
Enter fullscreen mode Exit fullscreen mode

The information hasn't necessarily changed.

Its representation has.

Conceptually:

Memory Representation
          │
          ▼
      Serializer
          │
          ▼
Wire Representation
Enter fullscreen mode Exit fullscreen mode

Then:

Wire Representation
          │
          ▼
     Deserializer
          │
          ▼
Memory Representation
Enter fullscreen mode Exit fullscreen mode

Serialization is therefore another geometric transformation between representational spaces.


27. Event-Driven Systems Form Graphs of Causality

Imagine:

OrderCreated
     │
     ├────► ReserveInventory
     │
     ├────► SendConfirmation
     │
     └────► UpdateAnalytics
Enter fullscreen mode Exit fullscreen mode

One event produces multiple paths.

This is a branching graph.

Later:

ReserveInventory
     │
     ▼
InventoryReserved
     │
     ├────► ShipOrder
     └────► UpdateStock
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Total:

$$
L = 5 + 10 + 20 + 50
$$

$$
L = 85ms
$$

But real distributed systems are rarely purely sequential.

Consider parallel execution:

              ┌──► Auth ──────── 10ms
Request ──────┤
              └──► Profile ───── 40ms
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
│    │    │    │
└────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

Many cross-connections.

System B

        API
      /  |  \
     ▼   ▼   ▼
    A    B    C
              │
              ▼
              D
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

How far does that change propagate?

Maybe:

Database
  │
  ▼
ORM Model
  │
  ▼
Repository
  │
  ▼
Service
  │
  ▼
API Serializer
  │
  ▼
Frontend
  │
  ▼
Mobile App
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The business logic is geometrically attached to the database.

Introduce an interface:

Business Logic
       │
       ▼
 Repository Interface
       ▲
       │
 PostgreSQL Adapter
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Now the service doesn't know whether the repository uses:

PostgreSQL
SQLite
MongoDB
Redis
HTTP
Memory
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

And:

repository = InMemoryUserRepository()
service = UserService(repository)

print(service.get_user(1))
Enter fullscreen mode Exit fullscreen mode

The geometry becomes:

             UserService
                 │
                 ▼
        UserRepository
          ▲          ▲
          │          │
      PostgreSQL   Memory
Enter fullscreen mode Exit fullscreen mode

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     │
└──────────────┘
Enter fullscreen mode Exit fullscreen mode

Each boundary is a security checkpoint.

Zero-trust architecture takes this further.

Instead of assuming:

Inside = trusted
Outside = untrusted
Enter fullscreen mode Exit fullscreen mode

we assume:

Every boundary requires verification.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Authorization can become a graph traversal problem.

For example:

User
 ↓
Organization
 ↓
Project
 ↓
Resource
 ↓
Permission
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The implementation changes.

But perhaps the architectural topology remains:

Service → Repository → Database
Enter fullscreen mode Exit fullscreen mode

Or:

React → API → Service → Database
Enter fullscreen mode Exit fullscreen mode

The exact implementation changed.

The structural relationships did not.

This is why architectural patterns survive technology changes.

You can replace:

Django
Enter fullscreen mode Exit fullscreen mode

with:

Laravel
Enter fullscreen mode Exit fullscreen mode

or:

Node.js
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You might introduce:

A → Facade → B
             C
             D
             E
Enter fullscreen mode Exit fullscreen mode

Now A has fewer direct relationships.

Or perhaps you detect:

A → B
B → C
C → A
Enter fullscreen mode Exit fullscreen mode

A cycle.

You might refactor to:

A → B → C
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

──────────────────────────────
Enter fullscreen mode Exit fullscreen mode

Road B:

──────╮
      │
      ╰──────╮
             │
             ╰────────
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Beautiful.

Now imagine:

user = service.create_user(data)
Enter fullscreen mode Exit fullscreen mode

actually does:

validate
 ↓
normalize
 ↓
check cache
 ↓
call remote service
 ↓
write database
 ↓
publish event
 ↓
invalidate cache
 ↓
send email
 ↓
schedule job
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

while internally performing twenty operations.

That is acceptable if the contract remains predictable.

A bad API might expose:

POST /orders
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

At time (t_0):

Primary = 100
Replica = 100
Enter fullscreen mode Exit fullscreen mode

At (t_1):

Primary = 120
Replica = 100
Enter fullscreen mode Exit fullscreen mode

At (t_2):

Primary = 120
Replica = 120
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
 \      /
  ─────
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But real systems often look like:

         ┌──► v2
v1 ──────┤
         └──► v3
Enter fullscreen mode Exit fullscreen mode

Or:

Client A ──► v1
Client B ──► v2
Client C ──► v3
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

becomes:

Tokens
   │
   ▼
AST
   │
   ▼
Control Flow Graph
   │
   ▼
SSA / IR
   │
   ▼
Optimized IR
   │
   ▼
Machine Instructions
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

The control flow graph is:

          Start
            │
            ▼
      balance > 0?
        /       \
      yes        no
       │          │
       ▼          ▼
   withdraw     reject
       \          /
        \        /
          ▼    ▼
           End
Enter fullscreen mode Exit fullscreen mode

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:
        ...
Enter fullscreen mode Exit fullscreen mode

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             │
        │          .             │
        │ .                 .    │
        └────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Tests sample points.

Property-based testing takes this idea seriously.

Instead of writing only:

assert add(2, 3) == 5
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

you get:

empty
huge
negative
malformed
random
nested
unicode
binary
truncated
unexpected
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

What happens at exactly:

age = 18
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Cross the boundary:

101 requests/minute
Enter fullscreen mode Exit fullscreen mode

and the system transitions into another region:

Allowed → Rate Limited
Enter fullscreen mode Exit fullscreen mode

Algorithms such as token buckets are essentially geometric capacity models.

A token bucket can be visualized as:

      ┌───────────────┐
      │   TOKENS      │
      │ ● ● ● ● ● ●   │
      └───────┬───────┘
              │
              ▼
          Request
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

creates temporal separation.

The producer doesn't need to wait for the consumer.

This transforms:

Producer → Consumer
Enter fullscreen mode Exit fullscreen mode

into:

Producer → Queue → Consumer
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

These questions often reveal performance problems faster than staring at source code.

For example:

Client
 ↓
Gateway
 ↓
Service
 ↓
Service
 ↓
Service
 ↓
Database
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

But architecture is also subtraction.

Sometimes the best optimization is:

A → B → C
Enter fullscreen mode Exit fullscreen mode

becoming:

A → C
Enter fullscreen mode Exit fullscreen mode

Sometimes the best performance improvement is:

Service → Service → Service
Enter fullscreen mode Exit fullscreen mode

becoming:

Service → Database
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

Look for:

  • cycles
  • supernodes
  • unnecessary dependencies
  • unstable dependencies

2. State geometry

Ask:

What states can exist?
What transitions are valid?
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

Look for:

  • races
  • stale reads
  • retries
  • timeouts
  • eventual consistency

5. Failure geometry

Ask:

What happens when every edge breaks?
Enter fullscreen mode Exit fullscreen mode

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
        }
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Now:

print(graph.dependencies_of("Orders"))
print(graph.degree("Orders"))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

An experienced engineer may mentally see:

HTTP
 ↓
Auth
 ↓
Validation
 ↓
Order Service
 ↓
Inventory
 ↓
Transaction
 ↓
Database
 ↓
Event
 ↓
Queue
 ↓
Notification
Enter fullscreen mode Exit fullscreen mode

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 = ...
Enter fullscreen mode Exit fullscreen mode

A function reads it without declaring the dependency.

The source code suggests:

function()
Enter fullscreen mode Exit fullscreen mode

The actual geometry is:

function()
    │
    └────► global state
Enter fullscreen mode Exit fullscreen mode

Dependency injection makes the edge visible:

def function(current_user):
    ...
Enter fullscreen mode Exit fullscreen mode

Now:

current_user ─────► function
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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ₙ
Enter fullscreen mode Exit fullscreen mode

A query becomes:

Find points near this point.

This is fundamentally different from:

WHERE name = 'Derek'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Graph-oriented API:

query {
    product(id: 42) {
        name
        price
        seller {
            name
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
             \     |     /
              \    |    /
               ────┴────
Enter fullscreen mode Exit fullscreen mode

The agent is effectively navigating a tool graph.

The problem becomes:

What tool?
What order?
What state?
What information?
What constraints?
What permissions?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

the system determines:

possible states
Enter fullscreen mode Exit fullscreen mode

and:

possible transitions
Enter fullscreen mode Exit fullscreen mode

and:

possible outputs
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)