4 min read

Detailed Design: Turning Big Ideas into Clean Components

High-Level Design gives you a cool diagram with boxes like “Authentication Service” or “Rate Limiter”. But what actually goes inside those boxes? Detailed Design (or Component Design) is where the rubber meets the road. In this guide, we walk through how to design an actual production-ready software component from scratch — algorithms, interfaces, thread-safety, and edge cases.

👉 Macro Blueprint: Want to see where components sit in the larger system? Read our guide on High-Level Design (HLD).


What is Detailed Design?

Imagine you are an architect designing an office building. You have drawn the floor plans showing where the conference rooms and elevators go. But when the electrician shows up, they need to know:

  • What voltage runs through this conduit?
  • Where does the circuit breaker panel live?
  • How many amps can this plug handle before a fuse blows?

That is Detailed Design. It takes one box from your high-level architecture diagram and zooms in until there is zero ambiguity left about how to write the code.


The Core Rule: Treat Your Component Like a Black Box

A well-designed component has a clear boundary. The rest of your application should only ever see a clean, public interface. Everything else — the internal state, the specific data structures, the helper methods — is strictly private.


flowchart TD
    Caller["Upstream Code / Controllers"]

    subgraph BlackBox["Component Boundary (Strictly Encapsulated)"]
        Interface["1. Public Interface Contract\n(Simple method calls: tryAcquire())"]
        Logic["2. Core Algorithmic Logic\n(Calculates time elapsed & token refills)"]
        State["3. Private Internal State\n(Thread-safe counters & timestamps)"]
        Outbound["4. Outbound Provider Interface\n(Pluggable Redis / Memory cache)"]

        Interface --> Logic
        Logic <--> State
        Logic --> Outbound
    end

    Caller -->|"Only talks to public interface"| Interface
    Outbound -->|"Talks to"| Redis[("External Redis Cache")]

Why this matters: If you decide tomorrow to swap an in-memory hash map for a distributed Redis cache, not a single line of caller code needs to change. Your component boundary protects the rest of the codebase from your internal refactoring.


A Concrete Example: Designing a Rate Limiter Component

Let's build something every senior backend engineer needs: an API Rate Limiter that restricts users to 60 requests per minute.

Step 1: Pick the Right Algorithm

A beginner might say: “Let's just keep an array of every timestamp the user makes a request, and check how many happened in the last 60 seconds!”

If you have 100,000 active users making thousands of requests, that array will eat gigabytes of RAM in no time.

Instead, we use the Token Bucket Algorithm:

  • Every user gets a virtual bucket holding up to 60 tokens.
  • Tokens refill smoothly over time (1 token every second).
  • When a request arrives, we don't store a list of timestamps — we just calculate tokens accrued since the last request, subtract 1 token, and update the timestamp.
  • The result: Memory consumption is strictly O(1) — just two numbers (token count and timestamp) per user!

sequenceDiagram
    autonumber
    actor Client as API Request
    participant Limiter as RateLimiter Component
    participant Store as State Storage (Redis / RAM)

    Client->>Limiter: 1. tryAcquire(user_id="user_42")
    activate Limiter
    Limiter->>Store: 2. Fetch {tokens: 15, last_refill: 1715000000}
    activate Store
    Store-->>Limiter: Return state
    deactivate Store

    Note over Limiter: Tokens refilled = (now - last_refill) * rate
Total available = min(60, 15 + refilled) alt Available Tokens >= 1 Limiter->>Store: 3. Save new state: {tokens: available - 1, last_refill: now} Limiter-->>Client: 4. Allowed! (HTTP 200 OK) else Bucket is Empty Limiter-->>Client: 4. Blocked! (HTTP 429 Too Many Requests) end deactivate Limiter

Step 2: Write the Interface Contract

Keep the contract minimal and easy to test:

// Public Interface: simple, clear, and focused
public interface RateLimiter {
    RateLimitResult tryAcquire(String clientId);
}

// Immutable Return DTO: gives callers exactly what they need
public record RateLimitResult(
    boolean isAllowed,
    long remainingTokens,
    long retryAfterSeconds
) {}

Concurrency: Where Component Design Usually Breaks

In production, two HTTP requests from the same user often arrive at the exact same millisecond. If your code is not designed for concurrency, here is what happens:

  1. Thread A reads: “User has 1 token left.”
  2. Thread B reads at the same instant: “User has 1 token left.”
  3. Thread A decrements to 0 and allows the request.
  4. Thread B decrements to 0 and allows the request.

Both requests slipped through! The user just cheated the rate limit.

How to fix it:

  • On a single server: Use atomic variables (like Java's AtomicReference) or lock-free compare-and-swap (CAS) loops. Avoid putting coarse synchronized locks around the whole method, which slows down all other users.
  • Across a distributed cluster: Run the token calculation inside an atomic Redis Lua script so that checking and decrementing tokens happen in a single uninterruptible step on the Redis server.

Detailed Design Review Checklist

Before you start writing code, ask yourself these four questions:

  • Is the interface clean? Does it hide the internal implementation details from callers?
  • What is the worst-case memory footprint? If 100,000 users hit this at once, will it run out of memory?
  • Is it thread-safe? Can two requests mutate the state simultaneously without race conditions?
  • Can I test it in memory? Can you test this component in a unit test in 5 milliseconds without needing a real database or network connection?

Related Guides:

Final Thoughts

Detailed Design is where good intentions become real, resilient software. By clarifying your component boundaries, choosing space-efficient algorithms, and thinking about concurrency before writing code, you save yourself hours of frantic debugging in production.

← PreviousDeep Dive into Deployment DesignNext →High-Level Design (HLD)