Fractal Architecture: The Only Code Structure That Fits in Your Head

Fractal Architecture: The Only Code Structure That Fits in Your Head

Why self-similar, vertical slices beat both layered and feature-factory architectures when cognitive load is the real bottleneck

Fractal Architecture: The Only Code Structure That Fits in Your Head

Why self-similar, vertical slices beat both layered and feature-factory architectures when cognitive load is the real bottleneck

Your working memory can hold roughly four chunks of information at once. Your codebase has seventeen layers of abstraction. Something has to give, and it’s usually your developers’ sanity.

In 1988, Australian psychologist John Sweller coined the term cognitive load, arguing that “our working memory is only able to hold a small amount of information at any one time.” Thirty-eight years later, we’re still designing systems that ignore this fundamental constraint. We’ve swung between technology-centric layered architectures that scatter features across folders, and feature-factory vertical slices that eliminate all structure in the name of autonomy. Both extremes fail. Fractal architecture might be the middle path we’ve been missing.

The Pendulum Problem: From Atlantis to Jira Hell

Remember when we thought breaking teams down by technical specialty would cut onboarding time? The logic seemed sound, let UI specialists handle presentation, domain experts own the business rules, and integration wizards manage the plumbing. The reality looked more like a city collapsing into the ocean.

The Clean Architecture approach groups code into Presentation, Application, Domain, and Infrastructure layers. In theory, business logic sits at the heart. In practice, developers spend their days tracing requests across four projects and a dozen abstractions, feeling like they’re trying to check the weather on their phone and ending up buying a skateboard after a doom-scrolling detour through social media.

Diagram contrasting Clean Architecture's Presentation, Application, Domain, and Infrastructure layers with Vertical Slice Architecture's self-contained feature slices
Clean Architecture groups code by technical layer, while Vertical Slice Architecture groups code by feature.

Adding a single feature means touching multiple projects: define the entity in Domain, add a command handler in Application, configure persistence in Infrastructure, wire up an endpoint in Presentation. Now multiply that by every feature your team ships, and you’ve created a system where understanding what you need to do requires a PhD, or at least a very patient senior developer.

Vertical Slices: The Pendulum Swings Too Far

So the pendulum swung. “No more layers!” the internet declared. “Just features, everything self-contained, zero shared code!”

Vertical Slice Architecture organizes code by feature instead of technical layer. Each slice is self-contained, cutting through UI, application logic, and data access. A simple “Get Order by ID” feature becomes one file instead of four files across four folders:

// Layered: related code is scattered
Controllers/OrdersController.cs    ← GetOrder, CreateOrder, DeleteOrder
Services/OrderService.cs           ← GetOrder, CreateOrder, DeleteOrder
Repositories/OrderRepository.cs    ← GetOrder, CreateOrder, DeleteOrder

// VSA: related code is co-located
Features/Orders/GetOrder.cs        ← everything for GetOrder
Features/Orders/CreateOrder.cs     ← everything for CreateOrder
Features/Orders/DeleteOrder.cs     ← everything for DeleteOrder

This works beautifully… until it doesn’t. The word “vertical” got semantically diffused into “no boundaries.” Teams started delivering vague ADRs with a few sentences describing an idea, then used each accepted slice as ammunition for the next vague request. The result: requirements drift, feature by feature, until you have a maze of duplication and responsibilities that can’t talk to each other.

Sound familiar? You’re not alone. Developers have watched their Jira boards transform into unreadable to-do lists where nobody can explain dependencies or priorities. The problem isn’t the tool, it’s the architecture that treats each feature as an autonomous island while they quietly share hidden coupling.

Fractal Architecture: Self-Similarity at Every Scale

Somewhere between technical layers that scatter related code and feature factories that eliminate all structure lies fractal architecture. The idea, championed by architects like Oskar Dudycz and Mark Seemann (author of Code That Fits in Your Head), is deceptively simple: apply the same composition pattern at every scale.

Animated zoom into a fractal, illustrating self-similarity at every scale
Self-similarity in a fractal: the same pattern repeats at every scale (Image: Wikimedia Commons).

Like mathematical fractals, the pattern repeats whether you’re looking at a system, a module, a component, or a single feature. Each level declares its dependencies explicitly, “here’s what I need from the outside”, and exposes its capabilities, “here’s what I offer to the outside.”

Consider an e-commerce system. At the highest level:

📁 e-commerce
    📁 shopping-carts
    📁 orders

Zoom into orders:

📁 orders
    📁 verifying-order
    📁 confirming-order
    📁 registering-order
    📁 pending-orders
    📁 order-storage

Zoom deeper into verifying-order:

📁 verifying-order
    📁 anti-fraud-detection
    📁 high-value-customer-verification
    📁 external-order-verification

The same pattern, capabilities with explicit dependencies, repeats at every level. Just like the C4 model embraced different zoom levels in documentation, fractal architecture does the same for code, without the hard limit of four.

The Dependency Declaration That Makes It Work

Here’s where fractal architecture gets practical. Each component explicitly declares its dependencies, what it needs from the outside world, and its public API, what it exposes to consumers.

import { type OrderStorage } from '../order-storage';

// Dependencies
export type CheckDriver = (id: DriverId) => Promise<DriverStatus>;

export type VerifyOrderDependencies = {
    storage: Pick<OrderStorage, 'getOrder' | 'saveOrder'>;
    drivers: {       
        checkDriver: CheckDriver;
    }
}

// Feature
export const verifyOrderHandler = async (
  {
    orderStorage: {
      getOrder,
      saveOrder
    },
    drivers: {
      checkDriver
    }
  }: VerifyOrderDependencies,
  command: VerifyOrder,
): Promise<void> => {
  const order = await getOrder(command.orderId);
  const driver = checkDriver(order.driverId);
  await saveOrder(verifyOrder({ ...command, driver }, order));
};

Now apply the same pattern at the module level:

import { verifyOrderHandler, type CheckDriver } from 'verifying-order';

// Dependencies
export type OrdersDependencies = {
    database: {
       connectionString: string
    },
    drivers: {       
        checkDriver: CheckDriver;
    }
}

// Capabilities
export const orders = async (
  props: OrdersDependencies,
  command: VerifyOrder,
): Promise<void> => {
  const db = drizzle(props.database.connectionString);
  const storage = ordersStorage(db);

  const verifyOrder = (command) => verifyOrderHandler(
    { 
        drivers: deps.drivers, // 👈 EXTERNAL
        storage // 👈 INTERNAL
    },
    command
  );

  return { 
     verifyOrder, // 👈 EXPOSES
  }  
};

See what just happened? The module declares what it needs (database connection string, driver checking) and what it offers (order verification). Internal composition, wiring storage to the verify handler, stays private. External dependencies flow in explicitly, never through hidden globals or service locators.

This isn’t about creating elaborate dependency injection frameworks. Internal calls to shared code can be direct and explicit. The point is that when a component genuinely needs something from the outside, it says so at the boundary, and when it offers functionality outward, it exposes a clear API.

When Layered Architecture Still Wins

Fractal architecture isn’t a silver bullet. Milan Jovanović’s analysis of when to choose Vertical Slice Architecture over Clean Architecture reveals a useful decision framework that applies equally to fractal composition:

Choose layers when:
Your domain is complex, rich business rules, invariants, and domain events need a dedicated domain layer to prevent duplication
Many features share domain logic, if ten features all need the same pricing calculation, a shared PricingService makes more sense than ten copies
You need strict architectural boundaries, layers enforce compile-time separation between presentation and infrastructure
Your team values uniformity, dozens of developers producing consistent code benefit from enforced patterns

Decision flow diagram: rich shared domain logic or strict compile-time boundaries lead to layered or Clean Architecture; independent, CRUD-heavy, or CQRS features lead to vertical slices; otherwise consider a hybrid approach
Decision framework for choosing between layered architecture and vertical slices.

But here’s the fractal insight: you don’t have to choose. The same self-similar composition pattern works at multiple levels. Use a shared domain layer for the business rules that genuinely span features. Use vertical slices for application logic. The evolution of architectural paradigms shows that the best teams consistently land on hybrids, not purity.

The Cognitive Load Payoff

Why does any of this matter? Because architecture is ultimately about fitting systems into human brains.

Layered architecture scatters related code across folders, demanding constant context switching. Vertical slices without structure collapse into feature factories with hidden coupling. Fractal architecture keeps what changes together in the same place, at every scale, and makes dependencies explicit at each boundary.

The result: when you pick up a component, whether it’s a system, module, or feature, you can understand what it does, what it needs, and what it offers, without understanding the entire application. Your working memory only needs to hold the current component’s shape and its immediate dependencies, not the whole distributed system’s complexity.

This has profound implications for team scaling and onboarding. New developers can start contributing to a module without understanding the entire system. Teams can work in parallel on different modules without merge conflicts. The architecture becomes a map that actually describes the territory, not a museum exhibit of what you thought the system looked like months ago.

Practical Steps to Fractalize

Ready to apply this without rewriting everything? Start incremental:

  1. New features go in a Features folder as self-contained slices. Each declares its dependencies explicitly at the interface boundary.
  2. Group slices by domain area as feature count grows. Features/Orders/, Features/Customers/, Features/Inventory/.
  3. Apply the pattern at the module level next. Each module declares its external dependencies and exposes its public capabilities.
  4. Extract shared logic only when it’s genuinely shared, not preemptively, and not lazily. If three features need the same validation, extract it. If one feature needs it, keep it local.
  5. Leave stable code alone. A feature nobody has touched in a year gains nothing from restructuring.

The automation trap applies here too: fractal architecture is a discipline, not a framework you can install. It requires teams to make dependencies explicit at every boundary, which means saying no to the seductive shortcut of “just import it directly.”

The Pattern That Fits

The debate between Clean Architecture and Vertical Slices will continue, but it’s a false dichotomy. Both approaches optimize for different things: layers manage complexity through discipline, slices manage it through isolation. Fractal architecture synthesizes both, self-similar composition that respects boundaries while keeping related code together.

The cognitive load crisis in software development isn’t going to solve itself. AI-generated code will only accelerate the erosion of human understanding if we don’t give developers structures they can actually hold in their heads. Fractal architecture isn’t just an elegant pattern, it’s a survival strategy for teams trying to build complex systems without losing their minds.

Stop bouncing between extremes. Start composing your application from self-similar components that fit in your head at every scale. Your developers, and your future self, will thank you.

Share:

Related Articles