17 minute read

LLMs have democratized software development in ways that seemed impossible just a few years ago. Tools like Lovable.com, Bolt.new, and v0.dev let non-technical founders build functional web applications through conversation. You describe what you want, the AI generates React components, and minutes later you have a working prototype. It feels like magic.

But there’s a complexity gap that nobody talks about. These same LLMs struggle profoundly when building distributed systems—multi-service architectures with complex state management, failure modes, consistency requirements, and operational concerns. The gap isn’t just about difficulty. It’s about whether the problem is fundamentally solvable with current approaches.

This post examines why no-code solutions exist for web UIs but not for high-volume, high-availability distributed applications. The answer reveals something important about the nature of complexity and the limits of AI-assisted development.

The No-Code Success Story

Let’s start with what works. No-code and low-code platforms for web development have achieved remarkable success:

Lovable.com (formerly GPT Engineer) generates full-stack web applications from natural language descriptions. You describe your app, and it produces React frontends with Supabase backends, complete with authentication, database schemas, and deployment configurations.

Bolt.new from StackBlitz creates instant full-stack environments in the browser. Describe a feature, and it generates the code, runs it in a WebContainer, and shows you the live result.

v0.dev from Vercel specializes in UI generation. You describe a component or page, and it produces production-quality React code with Tailwind styling.

Cursor and Claude Code accelerate development for engineers who know what they’re building. They autocomplete functions, refactor modules, and implement features from specifications.

These tools work because they target a specific complexity class: stateless or simply-stateful user interfaces with well-understood patterns.

Why Web UIs Are LLM-Friendly

Web UI development has characteristics that make it amenable to LLM generation:

Well-established patterns: React components, REST APIs, CRUD operations, form validation. These patterns appear millions of times in training data. LLMs have seen every variation.

Immediate visual feedback: You can see if a button is in the wrong place or a form doesn’t validate. The feedback loop is instant and obvious.

Forgiving failure modes: If a UI component has a bug, the worst case is usually a broken page or a failed form submission. Users retry or navigate away. The system doesn’t cascade into failure.

Limited state complexity: Most web UIs manage local state (form inputs, UI toggles) or simple server state (user profiles, lists of items). State machines are shallow.

Stateless or session-based: HTTP is request-response. Each request is independent or tied to a simple session. There’s no distributed consensus, no multi-phase commits, no complex coordination.

Standardized deployment: Vercel, Netlify, AWS Amplify. Deployment is a solved problem with clear conventions.

Rich component libraries: Material-UI, Chakra, shadcn/ui. LLMs can compose pre-built components rather than building from scratch.

This is the sweet spot for current LLMs. The problem space is constrained, patterns are well-documented, and feedback is immediate.

The Distributed Systems Complexity Wall

Now consider what happens when you try to build a distributed system with LLMs. Not a simple microservices demo—a production system that must handle high volume, maintain high availability, and operate reliably under failure conditions.

The characteristics are fundamentally different:

Novel architectural decisions: Every distributed system has unique requirements. Do you need strong consistency or eventual consistency? How do you partition data? What’s your failure recovery strategy? These aren’t pattern-matching problems—they’re design problems that require deep reasoning about tradeoffs.

Invisible failure modes: Distributed systems fail in ways that aren’t obvious until production. Network partitions, clock skew, cascading failures, thundering herds, split-brain scenarios. You can’t see these in a demo.

Complex state management: Distributed state requires coordination protocols (Raft, Paxos), distributed transactions (2PC, Saga), or careful eventual consistency designs (CRDTs, vector clocks). These aren’t library calls—they’re fundamental algorithms that must be implemented correctly.

Cascading failures: A bug in one service can bring down the entire system. Retry storms, resource exhaustion, deadlocks, and livelocks propagate across service boundaries.

Operational complexity: Deployment isn’t “push to Vercel.” It’s Kubernetes manifests, service meshes, observability pipelines, canary deployments, rollback strategies, and runbooks for incident response.

Testing requires simulation: You can’t test distributed systems by clicking through a UI. You need chaos engineering, fault injection, load testing, and distributed tracing to understand behavior.

Emergent behavior: The system’s behavior emerges from the interaction of components. You can’t reason about one service in isolation—you must understand the whole system.

The Fundamental Difference: Composition vs. Coordination

The gap between web UIs and distributed systems comes down to a fundamental difference in problem structure.

Web UIs are compositional: You build a page from components. Each component has clear inputs (props) and outputs (rendered DOM). Components compose predictably. If component A works and component B works, then A + B works.

Distributed systems require coordination: Services must agree on state, handle partial failures, and maintain invariants across network boundaries. If service A works and service B works, A + B might deadlock, lose data, or violate consistency guarantees.

LLMs excel at composition. They’ve seen millions of examples of components being combined. They can generate new combinations that follow established patterns.

LLMs struggle with coordination. Coordination requires reasoning about concurrent execution, failure scenarios, and system-wide invariants. These aren’t pattern-matching problems—they’re reasoning problems that require understanding causality, time, and distributed state.

A Concrete Example: Building a Job Queue

Let’s make this concrete with a simple example: building a distributed job queue.

The Web UI Version (LLM-friendly):

User: "Build a job queue UI where users can submit jobs, 
see job status, and view results when complete."

LLM generates:
- React form for job submission
- API endpoint that writes to database
- Polling component that fetches job status
- Results display component

Result: Works perfectly. Deploys to Vercel. Done in 30 minutes.

This is compositional. Each piece is independent. The LLM has seen thousands of similar examples.

The Distributed System Version (LLM-hostile):

User: "Build a distributed job queue that processes 10,000 jobs/second 
with at-least-once delivery guarantees, handles worker failures gracefully, 
supports job priorities, and maintains fair scheduling across tenants."

LLM must reason about:
- Message broker selection (Kafka? RabbitMQ? SQS? Custom?)
- Delivery semantics (idempotency, deduplication)
- Worker coordination (leader election, work stealing)
- Failure detection (heartbeats, timeouts)
- State management (job status, progress tracking)
- Priority scheduling (priority queues, preemption)
- Fair scheduling (weighted round-robin, quota enforcement)
- Observability (metrics, tracing, logging)
- Deployment (Kubernetes, service mesh, autoscaling)

Result: LLM generates plausible-looking code that has subtle bugs 
in failure handling, violates delivery guarantees under load, 
and creates deadlocks under specific timing conditions.

This requires coordination. The LLM must reason about distributed state, failure modes, and system-wide invariants. It can generate code that looks right but behaves incorrectly under production conditions.

Why SDLD Is Essential for Distributed Systems

Here’s where the story gets interesting. Spec-Driven LLM Development (SDLD) isn’t just helpful for distributed systems—it’s the enabling framework that makes LLM-assisted distributed systems development possible at all.

Without SDLD, you’re doing “vibe coding” at distributed systems scale, which is a recipe for disaster. With SDLD, you create the structure that lets experts encode their knowledge into specifications that LLMs can execute reliably.

SDLD is a comprehensive Software Development Lifecycle (SDLC) designed specifically for AI-assisted development at scale. I’ve written extensively about this methodology in Spec-Driven Development: A Comprehensive SDLC for AI-Assisted Software Engineering. The process outlined there—steering documents, three-file spec structure (requirements.md, design.md, tasks.md), build system integration, and CI/CD verification—is exactly what enables large-scale, accelerated development in complex domains like distributed systems.

The key insight from that methodology: treat specifications as first-class engineering artifacts, version them alongside code, and ensure AI assistants always work from documented requirements rather than conversational context. This transforms distributed systems development from an ad-hoc process into a systematic, verifiable workflow.

SDLD forces explicit failure mode enumeration: When you write EARS-format requirements, you must specify exactly what happens when workers crash, hang, or get partitioned. This forces you to think through failure modes systematically:

WHEN a worker fails to send a heartbeat for 30 seconds,
THE System SHALL mark the worker as failed and reassign its jobs

WHEN a worker reconnects after being marked failed,
THE System SHALL reject duplicate job completions using idempotency keys

IF a network partition occurs, WHEN the partition heals,
THE System SHALL use vector clocks to resolve conflicting state

These aren’t vague instructions—they’re testable contracts. The LLM implements exactly what you specified, and property-based tests verify the behavior.

SDLD captures coordination patterns: The design.md document in SDLD explicitly describes coordination mechanisms:

## Coordination Protocol

**Leader Election**: Use Raft consensus for coordinator selection
- **Property**: At most one leader per term
- **Validates**: Requirement 3.1 (single coordinator)

**Work Distribution**: Leader assigns jobs via priority queue
- **Property**: Higher priority jobs assigned first
- **Validates**: Requirement 3.2 (priority scheduling)

**Failure Detection**: Heartbeat-based with 30s timeout
- **Property**: Failed workers detected within 45s
- **Validates**: Requirement 3.3 (failure detection)

This isn’t just documentation—it’s the architectural blueprint that guides LLM implementation. The LLM doesn’t have to invent a coordination protocol; you’ve specified exactly which one to use and what properties it must satisfy.

SDLD enables incremental verification: The tasks.md file breaks implementation into verifiable steps:

- [ ] 1. Implement heartbeat mechanism
  - Property: Workers send heartbeat every 10s
  - Test: Property-based test with simulated clock
  - Validates: Requirement 3.3

- [ ] 2. Implement failure detection
  - Property: Workers marked failed after 30s silence
  - Test: Chaos test with network partition
  - Validates: Requirement 3.3

- [ ] 3. Implement job reassignment
  - Property: Failed worker's jobs reassigned within 60s
  - Test: Integration test with worker crash
  - Validates: Requirement 3.4

Each task is independently testable. You verify correctness incrementally rather than hoping the entire system works when assembled.

SDLD requires domain expertise—and that’s the point: Yes, writing specifications for distributed systems requires deep expertise. But that expertise must exist somewhere in your organization. SDLD doesn’t eliminate the need for experts—it amplifies their impact.

One distributed systems expert can write specifications that ten LLM-assisted engineers implement. The expert encodes their knowledge into requirements, design patterns, and verification properties. The LLMs execute the implementation. The tests verify correctness.

This is the leverage that makes distributed systems development with LLMs practical.

The Testing Gap

Testing reveals the gap most clearly.

Web UI Testing (LLM-friendly):

  • Unit tests for components (props in, DOM out)
  • Integration tests for API calls (mock responses)
  • E2E tests for user flows (Playwright, Cypress)
  • Visual regression tests (screenshot comparison)

LLMs can generate these tests because they follow patterns. The tests are deterministic and fast.

Distributed Systems Testing (LLM-hostile):

  • Unit tests (easy, but insufficient)
  • Integration tests (requires running multiple services)
  • Chaos engineering (inject failures, verify recovery)
  • Load testing (simulate production traffic)
  • Distributed tracing (verify request flows)
  • Formal verification (prove correctness properties)

LLMs struggle here because:

  • Tests require infrastructure (Kubernetes clusters, message brokers)
  • Tests are non-deterministic (timing-dependent, flaky)
  • Tests require domain knowledge (what failure modes to test?)
  • Tests take time (minutes to hours, not milliseconds)

The Operational Gap

Even if you could generate correct distributed systems code, operations remains a massive challenge.

Web UI Operations (LLM-friendly):

  • Deploy to Vercel/Netlify (one command)
  • Monitoring: error tracking (Sentry), analytics (Plausible)
  • Scaling: automatic (serverless)
  • Rollback: instant (previous deployment)

Distributed Systems Operations (LLM-hostile):

  • Deploy to Kubernetes (manifests, helm charts, operators)
  • Monitoring: metrics (Prometheus), logs (Loki), traces (Tempo)
  • Scaling: autoscaling policies, capacity planning
  • Rollback: canary deployments, traffic shifting, state migration
  • Incident response: runbooks, on-call rotation, postmortems

LLMs can generate Kubernetes manifests, but they can’t reason about capacity planning, failure scenarios, or incident response. These require operational experience and judgment.

Why No-Code Doesn’t Exist for Distributed Systems (But SDLD Does)

Now we can answer the original question with more nuance: Why do no-code solutions exist for web UIs but not for distributed systems?

The problem space is too large: Web UIs have constrained patterns. Distributed systems have unbounded architectural possibilities. No-code can’t enumerate all possibilities, but SDLD lets experts specify the right architecture for each system.

Failure modes are invisible: You can see a broken UI. You can’t see a distributed system that loses data under specific network conditions. SDLD forces explicit enumeration of failure modes in requirements, making them visible and testable.

Verification is intractable without structure: You can verify a UI by looking at it. Verifying a distributed system requires formal methods or extensive testing. SDLD provides the structure (properties, invariants, chaos tests) that makes verification tractable.

Expertise is required—and SDLD amplifies it: Non-technical founders can describe a UI. Describing a distributed system requires understanding distributed systems. SDLD doesn’t eliminate this requirement—it amplifies expert impact. One expert writes specs that ten engineers implement.

Feedback loops are slow without incremental verification: UI bugs are obvious immediately. Distributed systems bugs might only appear under production load. SDLD’s incremental task-based approach with property testing catches bugs early, tightening the feedback loop.

Composition doesn’t work, but coordination can be specified: UIs compose predictably. Distributed systems require coordination, which doesn’t compose. SDLD explicitly specifies coordination protocols (Raft, 2PC, Saga) and their properties, making coordination implementable and verifiable.

The fundamental insight: No-code works for web UIs because the patterns are universal. SDLD works for distributed systems because it lets experts encode system-specific patterns into specifications that LLMs can execute.

No-code is pattern reuse. SDLD is expert knowledge encoding.

The Spec-Driven Approach: The Only Way Forward

SDLD isn’t just helpful for distributed systems—it’s the only practical approach that works. The comprehensive methodology I describe in Spec-Driven Development: A Comprehensive SDLC for AI-Assisted Software Engineering provides the complete framework. Here’s how it applies specifically to distributed systems:

1. Expert-written specifications encode domain knowledge: A distributed systems expert writes specifications that capture:

  • Consistency requirements (strong, eventual, causal)
  • Failure modes and recovery strategies
  • Coordination protocols (consensus, distributed transactions)
  • Performance requirements (latency, throughput)
  • Operational concerns (deployment, monitoring, incident response)

This knowledge becomes reusable. Junior engineers and LLMs can implement from these specs without reinventing distributed systems theory.

2. EARS format makes requirements testable: Every requirement becomes a property that can be verified:

WHEN a client submits a job,
THE System SHALL assign a unique job ID
AND return the ID within 100ms
AND guarantee the job executes at least once

Property test:
- Generate 10,000 random job submissions
- Verify all IDs are unique
- Verify all responses < 100ms
- Verify all jobs execute (check completion log)

The specification directly translates to verification code. No ambiguity.

3. Design documents capture architectural decisions: The design.md file documents why you chose specific approaches:

## Why Kafka Over RabbitMQ

**Decision**: Use Kafka for job queue

**Rationale**:
- Need persistent log for replay (Requirement 2.3)
- Need partitioning for scale (Requirement 4.1)
- Need exactly-once semantics (Requirement 2.1)

**Tradeoffs**:
- Higher operational complexity
- Higher latency (10-50ms vs 1-5ms)
- Acceptable given durability requirements

This prevents LLMs from making arbitrary technology choices. The decisions are documented and justified.

4. Tasks provide incremental verification: Breaking implementation into small, testable tasks means you catch errors early:

- [ ] 1. Implement Kafka producer with idempotency
  - Test: Send duplicate messages, verify single delivery
  - Validates: Requirement 2.1

- [ ] 2. Implement consumer with offset management
  - Test: Crash consumer mid-processing, verify resume
  - Validates: Requirement 2.2

- [ ] 3. Implement dead letter queue for failures
  - Test: Inject processing failures, verify DLQ routing
  - Validates: Requirement 2.4

Each task is a checkpoint. If task 1 fails, you fix it before moving to task 2. This prevents cascading errors.

5. Property-based testing verifies invariants: SDLD specifications naturally translate to property-based tests:

// From Requirement 3.2: "For any set of jobs, 
// higher priority jobs execute before lower priority"
props.Property("priority ordering",
    prop.ForAll(
        func(jobs []Job) bool {
            queue := NewPriorityQueue()
            for _, job := range jobs {
                queue.Add(job)
            }
            
            var lastPriority int = math.MaxInt
            for !queue.Empty() {
                job := queue.Next()
                if job.Priority > lastPriority {
                    return false // Priority violation
                }
                lastPriority = job.Priority
            }
            return true
        },
        gen.SliceOf(genJob()),
    ))

The property test verifies the requirement across thousands of random inputs. This catches edge cases that example-based tests miss.

6. Chaos engineering validates failure handling: SDLD specs enumerate failure modes, which become chaos experiments:

Requirement 4.3: WHEN a worker crashes during job execution,
THE System SHALL reassign the job within 60 seconds

Chaos test:
1. Start 10 workers processing jobs
2. Kill random worker every 30 seconds
3. Verify all jobs complete
4. Verify no job executes more than twice (at-least-once)
5. Verify reassignment latency < 60s

The specification tells you exactly what to test. The chaos experiment verifies the system behaves correctly under failure.

The key insight: SDLD transforms distributed systems development from “hope the LLM gets it right” to “verify the implementation matches the specification.” The expert encodes knowledge into specs. The LLM implements from specs. The tests verify correctness. The feedback loop catches errors immediately.

This is how you build production distributed systems with LLM assistance. Not by hoping LLMs understand distributed systems, but by giving them precise specifications written by experts who do.

The Complexity Classes

We can categorize software by how amenable it is to LLM-assisted development:

Class 1: Fully Automatable (No-code territory)

  • Static websites
  • CRUD applications
  • Simple REST APIs
  • UI components
  • Form validation

Class 2: LLM-Accelerated with SDLD (Spec-driven development)

  • Complex web applications
  • API integrations
  • Data processing pipelines
  • Single-service backends
  • Standard microservices

Class 3: SDLD-Enabled Distributed Systems (Expert-guided with SDLD)

  • Multi-service distributed systems
  • Consensus protocols
  • Distributed transactions
  • High-performance systems
  • High-availability systems

Class 4: Human-Required (LLMs provide limited value even with SDLD)

  • Novel distributed algorithms research
  • Formal verification proofs
  • Initial architecture design
  • Incident response
  • Capacity planning

No-code solutions exist for Class 1. Spec-driven development works for Class 2. Class 3 requires experts using LLMs as tools. Class 4 requires human expertise with minimal LLM assistance.

The Future: Narrowing the Gap

The gap between web UIs and distributed systems won’t disappear, but it might narrow:

Better reasoning models: Models like o1 and o3 show improved reasoning about complex systems. Future models might handle coordination problems better.

Formal methods integration: LLMs that can generate formal specifications and proofs could verify distributed systems properties.

Simulation environments: Better tools for simulating distributed systems could provide faster feedback loops for LLM-generated code.

Domain-specific models: Models trained specifically on distributed systems code and literature might understand coordination patterns better.

Hybrid approaches: Humans design architecture and specify invariants, LLMs implement components and generate tests.

But even with these advances, distributed systems will remain fundamentally harder than web UIs because coordination is harder than composition.

Practical Implications

For engineering teams, this analysis has practical implications:

If you’re building web UIs: LLMs and no-code tools are production-ready. Use them aggressively. SDLD ensures quality when complexity increases.

If you’re building distributed systems: SDLD isn’t optional—it’s the enabling framework. Invest in:

  • Distributed systems expertise to write specifications
  • Rigorous SDLD workflow (requirements → design → tasks)
  • Property-based testing for invariant verification
  • Chaos engineering for failure mode validation
  • Operational excellence (observability, incident response)

If you’re a founder: Understand the complexity class of your product. Web UIs can be built with no-code tools. Distributed systems require experienced engineers using SDLD to amplify their impact with LLM assistance.

If you’re learning: Start with web UIs to understand LLM-assisted development. Learn SDLD methodology. Study distributed systems fundamentals. Then apply SDLD to distributed systems with expert guidance.

If you’re a distributed systems expert: SDLD is your force multiplier. Your specifications enable teams to build systems that would otherwise require your direct involvement in every implementation detail. Write specs, not code.

Conclusion

The LLM complexity gap is real, but SDLD bridges it. No-code solutions exist for web UIs because UIs are compositional, have well-established patterns, and provide immediate feedback. Distributed systems require coordination, have invisible failure modes, and demand deep expertise—but SDLD provides the framework that makes LLM-assisted distributed systems development practical.

The gap isn’t insurmountable. It requires the right methodology. SDLD transforms distributed systems development from “hope the LLM gets it right” to “verify the implementation matches expert-written specifications.” The complete process—from steering documents through build system integration to CI/CD verification—is detailed in Spec-Driven Development: A Comprehensive SDLC for AI-Assisted Software Engineering.

This is why Lovable.com can generate web apps but there’s no equivalent no-code solution for high-volume, high-availability distributed systems. Web UIs don’t need SDLD—the patterns are universal. Distributed systems require SDLD—each system needs expert-designed specifications.

But here’s the important part: SDLD makes distributed systems development with LLMs not just possible, but practical. One distributed systems expert can write specifications that enable a team of LLM-assisted engineers to build production systems. The expert encodes knowledge into requirements, design patterns, and verification properties. The LLMs execute implementations. The tests verify correctness. The feedback loop catches errors immediately.

The solution isn’t to avoid LLMs for distributed systems. It’s to use SDLD as the enabling framework: expert-written specifications, incremental implementation with verification, property-based testing, and chaos engineering. This is how you build production distributed systems with LLM assistance.

The gap between web UIs and distributed systems reflects the gap between composition and coordination. SDLD doesn’t eliminate that gap—it provides the structure that lets experts encode coordination patterns into specifications that LLMs can implement reliably.

The tools are getting better. The models are improving. But the methodology matters most. SDLD is the methodology that makes distributed systems development with LLMs practical, scalable, and reliable.

And that’s not a limitation—it’s an opportunity. Distributed systems experts can now amplify their impact by writing specifications rather than implementations. Teams can build complex systems faster without sacrificing correctness. The complexity gap remains, but SDLD provides the bridge.


David Lapsley is CTO at Actualyze.ai, focused on distributed systems and cloud infrastructure. Previously VP of Engineering at Metacloud (acquired by Cisco) and AWS executive driving Intent-Driven Networking for AI workloads.