System Design Method and Requirement Discovery

Published by Programming Line in System Design and Distributed Systems. Last updated .

Purpose: turn a vague prompt into design inputs

System design does not begin with databases, queues, or cache diagrams. It begins with requirement discovery: the disciplined work of converting an ambiguous product idea into decisions the architecture can satisfy. In this lesson, the outcome is a repeatable method for identifying actors, user journeys, functional behavior, quality attributes, constraints, assumptions, estimates, and acceptance tests. The point is not to produce a large specification. The point is to avoid designing the wrong system with confidence.

A prompt such as design a notification service hides basic questions: who sends notifications, which channels matter, how quickly users must receive them, what happens when a provider is down, and whether duplicates are acceptable. Requirement discovery exposes those questions early. It also separates what the system must do from how the system may do it, which lets a designer compare architectures instead of defending the first diagram that comes to mind.

The internal method

A practical discovery method has five passes. First, define the system boundary. Name users, external systems, and responsibilities that are inside or outside the design. Second, discover functional requirements by walking concrete user journeys. Third, discover nonfunctional requirements by asking what qualities make those journeys acceptable: latency, availability, durability, consistency, privacy, cost, operability, and scalability. Fourth, quantify important requirements with units, targets, and measurement points. Fifth, translate requirements into design pressure: the architectural forces that make one solution preferable to another.

The boundary pass prevents scope drift. For a ride matching service, matching drivers to riders may be in scope, while payment settlement and tax reporting may be external systems. Saying payments are out of scope is weaker than saying the checkout service calls an external payment processor and stores only the resulting payment status and provider reference.

The journey pass discovers functional requirements. A journey is a short story with an actor, trigger, system action, and result. User uploads a profile photo is useful, but mobile user uploads a 5 MB JPEG, sees the new avatar on their profile, and old cached avatars expire within ten minutes is better. That wording reveals storage, validation, processing, and cache invalidation requirements without choosing implementation too early.

The quality pass discovers nonfunctional requirements. These are not decoration. A chat system with a ten-second delivery target can use different mechanisms from a trading alert system with a sub-second target. A log analytics product with eventual index freshness can batch aggressively; a fraud decision service usually cannot. The quality pass asks each requirement: how fast, how often, how much data, how many users, how correct, how recoverable, and how auditable?

The quantification pass turns adjectives into measurable claims. Fast becomes p95 message fanout under 300 ms after accepting the write. Reliable becomes 99.9 percent monthly availability for read APIs, excluding scheduled maintenance. Large scale becomes 20,000 writes per second at peak, 100 million stored objects, and 30-day hot retention. When numbers are unknown, mark them as assumptions and attach validation work.

Requirement anatomy

A useful requirement has a name, type, actor or workload, target, measurement method, priority, and rationale. Type distinguishes functional behavior from quality attributes and hard constraints. Priority helps trade-offs: a legal retention constraint is not negotiated like a nice-to-have export format. Rationale records why the requirement exists, which matters when later evidence changes.

requirements:
  - id: FR-1
    type: functional
    actor: registered_user
    statement: "Create a short post with text and optional media."
    priority: must
    acceptance: "Post is visible on the author's profile after the API returns success."
  - id: QR-1
    type: latency
    workload: feed_read
    target: "p95 <= 250 ms for home feed reads during normal peak"
    priority: should
    measurement: "server-side API histogram excluding client network time"
  - id: C-1
    type: constraint
    statement: "Deleted user content must be removed from search results within 24 hours."
    priority: must
    rationale: "Privacy and policy commitment"

This format is intentionally implementation neutral. It does not say whether the feed is precomputed, fanout-on-write, fanout-on-read, or hybrid. It gives the designer enough information to evaluate those options. If the feed read target is strict and celebrity fanout is large, a hybrid feed strategy may become attractive. If search removal has a 24-hour target, asynchronous index cleanup may be acceptable.

Example 1: clarifying a URL shortener

Start with design a URL shortener. A weak answer jumps to a key-value store. Requirement discovery starts with actors and journeys. A creator submits a long URL and receives a short code. A visitor opens the short link and is redirected. An admin disables abusive links. Those journeys reveal functional requirements: create mapping, redirect, handle invalid codes, prevent obvious abuse, and collect click metrics if needed.

The first quantitative pass might estimate 100 new links per second, 20,000 redirects per second at peak, and 5 years of retention. The critical quality attribute is redirect latency, because it is user-facing and repeated far more often than creation. A target such as p95 redirect decision under 50 ms inside the service region pushes the design toward a read-optimized store, hot-code caching, and simple lookup. Strong consistency during creation may matter for read-after-write of a new short code, not for metrics aggregation.

Expected behavior is deterministic for core cases: creating a valid long URL returns a unique short code; resolving an existing active code returns an HTTP redirect target; resolving a missing or disabled code returns an error page or 404/410 response depending on policy. Click metrics are less deterministic because aggregation can be delayed, so their freshness requirement must be stated separately.

Example 2: discovering notification requirements

For design a notification service, the boundary matters more. Is this service responsible for user preferences, template rendering, provider delivery, retry scheduling, and audit history, or only dispatch? A useful boundary might say: product services submit notification requests; this service validates consent and preferences, renders approved templates, sends email and push through external providers, records delivery attempts, and exposes status lookup.

Functional requirements include submit notification, choose eligible channels, render localized templates, respect unsubscribe preferences, retry transient provider failures, and store an audit trail. Quality requirements include accepting requests at 2,000 per second during campaigns, delivering transactional messages within 30 seconds at p95, and ensuring marketing messages do not delay password reset messages. That last requirement suggests priority queues or workload isolation. A single FIFO queue may satisfy average throughput while still violating the product requirement during bursts.

Expected behavior: if a user has disabled marketing email but enabled security email, a password reset email is sent and a promotion email is suppressed with a recorded reason. If a provider returns a temporary error, the service schedules retries with backoff. If a provider returns a permanent invalid-address error, retrying stops.

Example 3: estimating pressure for a feed

A social feed example shows why estimates belong inside requirement discovery. Suppose the system has 10 million daily active users, each reads the feed 20 times per day, and each read returns 30 items. Suppose 1 million users create 2 posts per day. A small script can turn these assumptions into workload pressure.

from dataclasses import dataclass

@dataclass(frozen=True)
class FeedAssumptions:
    daily_active_users: int
    reads_per_user_per_day: int
    posts_per_creator_per_day: int
    daily_creators: int

assumptions = FeedAssumptions(
    daily_active_users=10_000_000,
    reads_per_user_per_day=20,
    posts_per_creator_per_day=2,
    daily_creators=1_000_000,
)
seconds_per_day = 86_400
read_qps = assumptions.daily_active_users * assumptions.reads_per_user_per_day / seconds_per_day
write_qps = assumptions.daily_creators * assumptions.posts_per_creator_per_day / seconds_per_day
print(round(read_qps))
print(round(write_qps))

The expected output is:

2315
23

The result is not the final capacity plan, because traffic has peaks and celebrity fanout is uneven. It still teaches an important design fact: reads dominate writes by roughly two orders of magnitude. That pressure affects whether to compute feeds at read time, write time, or with a hybrid. If p95 feed reads must stay below 250 ms, pure fanout-on-read may struggle for users following many active accounts. If write latency for posters must stay low, pure fanout-on-write can struggle when a user has millions of followers.

Design choices and trade-offs

The main design choice in requirement discovery is depth. Too little discovery produces a generic architecture. Too much discovery delays learning and creates false precision. A pragmatic method captures enough detail to compare the next major choices. For an interview, that may mean asking five to eight high-value questions and stating assumptions. For a production project, it may mean workshops, analytics review, risk analysis, and written acceptance criteria.

Another trade-off is between product language and engineering language. Product language is good at intent: users should see relevant posts quickly. Engineering language is good at testability: home feed API p95 below 250 ms for logged-in users in the primary region. The best requirement set keeps both. Rationale preserves intent, while target and measurement make the requirement testable.

Prioritization is also a design choice. MoSCoW labels, numeric priority, or severity levels all work if the team uses them consistently. The key is to separate hard constraints from preferences. A data residency rule may force regional storage even if it increases cost. A p99 latency target may be relaxed if it only affects an internal admin report.

Failure modes and troubleshooting

A common failure is designing from nouns instead of behavior. The symptom is a diagram full of database, cache, queue, and service boxes while nobody can explain what happens for a concrete user action. The cause is skipping journeys. Diagnose it by selecting one critical path and asking: who calls whom, what data changes, what response is returned, and what can fail? Correct it by rewriting inputs as actor-trigger-result journeys.

Another failure is unmeasurable quality requirements. The symptom is language like highly scalable, low latency, or eventually consistent with no unit or measurement point. The cause is accepting adjectives as requirements. Diagnose it by asking whether a test could pass or fail. Correct it by adding workload, percentile, time window, and measurement location. For example, search indexing should be quick becomes new public posts appear in search within 60 seconds at p95.

A third failure is hidden conflict. The symptom is an architecture that satisfies each requirement in isolation but not together, such as strict global consistency plus sub-100 ms writes from every continent. The cause is missing trade-off analysis. Diagnose it by pairing requirements and asking whether the same mechanism can satisfy both under failure. Correct it by changing priority, narrowing scope, introducing regional behavior, or documenting an explicit product compromise.

Security, performance, and reliability implications

Requirement discovery is where many security and reliability decisions first become visible. If the system stores personal data, requirements must identify retention, deletion, access control, audit, and data residency obligations. A requirement that deleted content disappears from search within 24 hours implies deletion propagation, index tombstones, or periodic reconciliation. A requirement that support agents can view sensitive records implies role-based access, audit logging, and possibly data masking.

Performance requirements shape data placement and computation timing. Read-heavy workloads favor precomputation, caching, denormalization, and indexes. Write-heavy workloads favor append-only ingestion, batching, partitioning, and asynchronous processing. Reliability requirements shape redundancy and recovery. If an ordering service must continue accepting orders during a single-zone failure, the design needs zone-independent capacity and tested failover. If analytics can pause for an hour, the design can be cheaper and simpler.

Hands-on lab: build a requirement brief

Prerequisites: choose a small system idea, such as a bookmark manager, shared calendar, file upload service, or comment system. You need a text editor and enough domain knowledge to describe three user journeys. No cloud account is required.

  1. Write one sentence defining the system boundary. Include one explicit out-of-scope responsibility.
  2. List three actors or external systems.
  3. Write five functional requirements as actor-trigger-result statements.
  4. Write five quality or constraint requirements with units where possible.
  5. Estimate one read workload, one write workload, and one storage workload.
  6. Mark each requirement as must, should, or could.
  7. Select two requirements that create tension and describe the trade-off.
  8. Define two acceptance checks that would prove the design meets the requirement.

You can validate the brief with this small checker. It does not judge product quality; it catches missing fields that usually make later architecture discussion weak.

requirements = [
    {"id": "FR-1", "type": "functional", "priority": "must", "statement": "User saves a bookmark and sees it in their list."},
    {"id": "QR-1", "type": "latency", "priority": "should", "target": "p95 list load <= 200 ms for 100 saved bookmarks"},
    {"id": "C-1", "type": "constraint", "priority": "must", "statement": "Private bookmarks are visible only to their owner."},
]
required_keys = {"id", "type", "priority"}
missing = {item["id"]: sorted(required_keys - item.keys()) for item in requirements if required_keys - item.keys()}
print(missing)

The expected output is:

{}

Verification: every must requirement should have an observable pass or fail condition, every numeric target should name a measurement point, and every estimate should include the assumption behind it. Cleanup is simple: archive the brief with the design notes. If later evidence changes an assumption, revise the requirement rather than silently changing the architecture.

Assessment exercises

  1. A team says its image upload service must be fast and reliable. Rewrite this as two measurable requirements, including workload and measurement point.
  2. For a collaborative document editor, name three functional requirements and three quality requirements. Which one is most likely to dominate the architecture, and why?
  3. A product manager asks for global writes under 50 ms and strongly consistent reads everywhere. Explain the conflict and propose two ways to narrow or reprioritize the requirement.
  4. Given a feed system with far more reads than writes, explain how that estimate changes the fanout strategy discussion.
  5. Choose one requirement from the lab and describe a failure symptom, likely cause, diagnostic step, and correction.

Summary

Requirement discovery is the first design mechanism in system design. It converts vague prompts into boundaries, journeys, functional behavior, quality attributes, constraints, estimates, priorities, and acceptance checks. Done well, it reveals architectural forces before components are selected. The method is simple: define the boundary, walk concrete journeys, quantify qualities, record assumptions, expose trade-offs, and keep requirements testable. That discipline connects directly to every later system design topic because storage, networking, caching, queues, consistency, and reliability choices only make sense when the requirements they serve are clear.