A Pragmatic Framework for System Design: Spiral vs. Incremental MVP Approaches

Arpit Bhayani

Arpit Bhayani

Apr 13, 2022 • 8 min read

Play

A Pragmatic Framework for System Design: Spiral vs. Incremental MVP Approaches

System design is often perceived as inherently difficult, ambiguous, and overwhelming. However, large-scale systems do not need to be designed through chaotic guesswork. Whether architecting a real-world enterprise platform or navigating an open-ended technical design session, success relies on having a structured mental framework to decompose business requirements into resilient technical abstractions.

To build scalable architectures systematically, engineers must understand the scope of what constitutes a system, distinguish between the three layers of architectural design, and choose an appropriate evolution model: the Spiral Approach or the Incremental MVP Approach.


Deconstructing “System Design”

At its core, system design is the process of translating customer requirements into concrete product specifications, and then synthesizing those specifications into engineering realities.

Customer Need ──► Product/Business Spec ──► Technical Requirements ──► System Architecture

A “system” is not merely a collection of web servers and databases. Depending on the context, a system can be:

  • An End-to-End Application: A full-stack solution encompassing frontend clients, API gateways, backend services, and persistent stores (e.g., an interactive live-class platform or an e-commerce checkout flow).
  • A Specialized Microservice or Platform: A purely internal engineering solution built to serve other engineering teams (e.g., an internal authentication service, an ingestion engine, or an ID-generation service).
  • A Reusable Library or Framework: Core building blocks that solve low-level concerns uniformly across an organization (e.g., an optimized HTTP connection-pooling client or an in-house RPC framework).
  • Embedded Systems or Hardware: Tightly constrained physical hardware, network appliances, or IoT devices.

The Three Layers of System Design

When evaluating or designing any system, architectural discussions typically span three distinct lenses:

graph TD
    A[High-Level Architecture] -->|Defines boundaries & topologies| B[Logical Design]
    B -->|Defines algorithms, schemas & structures| C[Physical Design]
    C -->|Defines compute, I/O, storage & deployment| D[Production Reality]

1. High-Level Architecture (Macro View)

This is the bird’s-eye view of the system. It identifies high-level components and communication paths without getting bogged down in implementation minutiae:

  • Service topology and component boundaries (e.g., API Gateway, User Service, Notification Service).
  • Communication protocols (e.g., synchronous REST/gRPC vs. asynchronous message pub/sub).
  • High-level persistence mechanisms and caching boundaries.

2. Logical Design (Deep-Dive Mechanics)

Logical design zooms into the internal mechanics of individual components. It answers how a specific service solves its algorithmic and transactional challenges:

  • Domain entities, schemas, and relational models.
  • Core data structures (e.g., LSM-trees vs. B+ Trees for storage engines, Redis Sorted Sets for leaderboards).
  • Business logic, validation rules, state machines, and concurrency control models (optimistic vs. pessimistic locking).

3. Physical Design (Infrastructure & Capacity)

Physical design addresses the bare-metal realities of running the system in production:

  • Compute and memory profiles (e.g., IO-optimized vs. compute-optimized cloud instances, GPU acceleration).
  • Disk I/O, storage throughput (IOPS), and network interface limits.
  • Backup, disaster recovery (DR), replication topologies, and network isolation (VPCs, subnets).

Interview vs. Work Context: In production engineering, you are responsible for delivering all three layers. In a time-constrained system design interview, you must explicitly align on which layers to prioritize, usually starting with high-level architecture before diving deep into one or two critical logical/physical bottlenecks.


Two Paradigms to Approach System Design

Architecting complex platforms can be approached through two distinct mental models depending on problem familiarity and certainty.

1. The Spiral Approach (Core-Outward Evolution)

The Spiral Approach starts from the foundational center of the system—often the primary data store or core algorithmic engine—and expands outward in predictable, concentric cycles.

graph LR
    Core[1. Core Persistence / Data Model] --> Compute[2. API Layer & Workers]
    Compute --> Services[3. Surrounding Microservices]
    Services --> Reliability[4. Retries, DLQs & Edge Layers]

How it Works:

  1. Establish the Core: Identify the definitive source of truth or state machine (e.g., relational ledger for payments).
  2. Layer the Primary APIs: Build direct CRUD interfaces and application servers that interact directly with that core.
  3. Expand Component Boundaries: Add dependent auxiliary services (e.g., payment gateways, fraud detection).
  4. Incorporate Fault-Tolerance: Add asynchronous retry workers, dead-letter queues, and circuit breakers.

When to Use:

  • High Domain Familiarity: You have solved this problem or a similar variant before.
  • Predictable Constraints: The read/write characteristics, latency targets, and operational patterns are clear from Day 1.
  • Established Patterns: Standard transactional systems where getting the data model and consistency guarantees right at the center dictates everything else.

2. The Incremental MVP Approach (Scale-Triggered Evolution)

The Incremental MVP Approach models systems as non-concentric, evolving structures. Instead of planning the end-state architecture immediately, you start with an absolute baseline (Day 0 Architecture) and incrementally introduce architectural complexity strictly as specific bottlenecks arise.

graph TD
    D0[Day 0: Single Server + Single DB] -->|Scale Trigger: CPU/Memory Bottleneck| D1[Add Reverse Proxy / Load Balancer + Horizontal App Servers]
    D1 -->|Scale Trigger: DB Read Saturation| D2[Introduce Read Replicas & In-Memory Cache]
    D2 -->|Scale Trigger: Heavy Sync Processing Latency| D3[Introduce Asynchronous Message Broker & Background Workers]

Step-by-Step Evolution:

  1. Day 0 (The Bare Minimum): Client directly queries a single monolithic API server backed by a single relational database instance.
  2. Bottleneck: Compute Exhaustion: As concurrent users grow, the single server maxes out CPU and connections.
    • Evolution: Introduce a Load Balancer (e.g., NGINX/HAProxy/ALB) and scale the application tier horizontally with stateless instances.
  3. Bottleneck: Database Read Saturation: The application tier overloads the single primary database with read queries.
    • Evolution: Split read and write traffic by provisioning read replicas, or place an in-memory cache (e.g., Redis) in front of the database for hot keys.
  4. Bottleneck: Synchronous Request Timeouts: Heavy tasks (e.g., image processing, email dispatches, audit logging) block request-response lifecycles.
    • Evolution: Introduce an asynchronous message broker (e.g., RabbitMQ, Apache Kafka, Amazon SQS) and dedicated consumer workers to decouple synchronous user actions from background processing.

When to Use:

  • Novel or Ambiguous Problems: The traffic volume, user interaction patterns, and performance characteristics are largely unknown.
  • Early-Stage Engineering: Avoiding premature optimization and over-engineering.
  • Interview Environments: It allows you to demonstrate your architectural thought process by showing why a particular component (cache, queue, replica) was introduced in direct response to a concrete limitation.

Comparison: Spiral vs. Incremental MVP

DimensionSpiral ApproachIncremental MVP Approach
Starting PointThe core data model, storage engine, or central state machine.Minimal Day-0 end-to-end flow (Single Client, App, DB).
Evolution MechanismConcentric expansion by layering surrounding services and safeguards.Responsive scaling driven by specific bottleneck triggers.
Optimal Use CaseWell-defined problems with predictable throughput, consistency, and storage profiles.High ambiguity, greenfield systems, or exploratory interviews.
Primary RiskDesigning the wrong core if domain assumptions change mid-way.Accumulating tech debt or patching symptoms without fixing architectural root causes.

Four Invariant Principles for System Design

Regardless of which architectural paradigm you choose, four fundamental principles should guide every design decision:

1. Systems are Infinitely Buildable—Fence the Scope

Any system can be expanded infinitely with additional edge-case handling, global multi-region deployments, automated failover automation, and speculative caching. Because time and engineering resources are bounded, fencing the scope is non-negotiable.

  • Define clear Non-Goals upfront.
  • Establish explicit boundary conditions (e.g., “For this iteration, we support single-region deployment with a recovery time objective (RTO) of 15 minutes.”).

2. Ambiguity Demands Clarification

Requirements are inherently incomplete. Before designing, seek explicit clarifications regarding operational thresholds:

  • What is the expected read-to-write ratio?
  • Are we optimizing for high availability or strong consistency (CAP theorem trade-offs)?
  • What are the SLA/SLO expectations? Can we tolerate occasional eventual consistency, or is read-after-write consistency required?
  • What is the tolerable downtime during catastrophic failure?

3. Challenge Every Default Assumption

Exceptional engineers do not default to standard industry buzzwords without justification. Challenge every decision using first principles:

  • Why REST instead of gRPC? (Does the internal network benefit from binary serialization and HTTP/2 multiplexing?)
  • Why WebSockets instead of short polling or Server-Sent Events (SSE)? (Is bi-directional streaming strictly required, or is server-push sufficient?)
  • Why NoSQL over Relational? (Do we actually need dynamic horizontal sharding, or are we sacrificing ACID guarantees unnecessarily?)

4. Divide and Conquer

Complex architectures cannot be solved as a single monolithic block. Break large distributed problems into distinct, loosely coupled subsystems:

  1. Isolate the ingestion path from the querying path (e.g., CQRS pattern).
  2. Isolate stateful storage engines from stateless compute layers.
  3. Solve each subsystem’s data structures, communication protocols, and scaling limits independently, then integrate them via strict interfaces.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses