What is System Design?

Definition

System design is the process of defining the architecture, components, modules, interfaces, and data for a system to satisfy specified requirements. It involves making high-level decisions about how different parts of a system work together to achieve business goals while meeting non-functional requirements like scalability, reliability, and performance.

Key Objectives

const systemDesignObjectives = {
  scalability: 'Handle growing amounts of work',
  reliability: 'System works correctly under failures',
  availability: 'System is operational when needed',
  performance: 'Fast response times and throughput',
  maintainability: 'Easy to modify and extend',
  security: 'Protect data and prevent unauthorized access',
  costEfficiency: 'Optimize resource usage and costs'
};

System Design Process

1. Requirements Gathering

// Functional Requirements
const functionalRequirements = {
  userManagement: 'Users can register, login, update profile',
  contentCreation: 'Users can create, edit, delete posts',
  socialFeatures: 'Users can follow, like, comment',
  search: 'Users can search for content and users'
};

// Non-Functional Requirements
const nonFunctionalRequirements = {
  scalability: '10 million daily active users',
  availability: '99.99% uptime',
  latency: 'P99 < 200ms',
  throughput: '10,000 requests per second',
  dataRetention: 'Store data for 5 years',
  compliance: 'GDPR compliant'
};

2. Capacity Estimation

// Calculate storage requirements
class CapacityEstimator {
  calculateStorage(users, avgPostsPerUser, avgPostSize) {
    const totalPosts = users * avgPostsPerUser;
    const storageGB = (totalPosts * avgPostSize) / (1024 ** 3);
    
    return {
      totalPosts,
      storageGB,
      storageWithReplication: storageGB * 3, // 3x replication
      storagePerYear: storageGB * 0.2 // 20% growth per year
    };
  }
  
  calculateBandwidth(requestsPerSecond, avgResponseSize) {
    const bytesPerSecond = requestsPerSecond * avgResponseSize;
    const mbps = (bytesPerSecond * 8) / (1024 ** 2);
    
    return {
      bytesPerSecond,
      mbps,
      gbPerDay: (bytesPerSecond * 86400) / (1024 ** 3)
    };
  }
}

// Example calculation
const estimator = new CapacityEstimator();

// 10M users, 100 posts per user, 1KB per post
const storage = estimator.calculateStorage(10_000_000, 100, 1024);
console.log(`Storage needed: ${storage.storageGB.toFixed(2)} GB`);

// 10K requests/sec, 50KB average response
const bandwidth = estimator.calculateBandwidth(10_000, 50 * 1024);
console.log(`Bandwidth needed: ${bandwidth.mbps.toFixed(2)} Mbps`);

3. High-Level Design

// Define system components
const systemArchitecture = {
  clientLayer: {
    webApp: 'React/Angular SPA',
    mobileApp: 'iOS/Android native',
    cdn: 'CloudFront for static assets'
  },
  
  apiLayer: {
    loadBalancer: 'AWS ALB',
    apiGateway: 'Kong/AWS API Gateway',
    services: ['User Service', 'Post Service', 'Feed Service']
  },
  
  dataLayer: {
    primaryDB: 'PostgreSQL for relational data',
    cache: 'Redis for session and hot data',
    objectStorage: 'S3 for media files',
    searchEngine: 'Elasticsearch for full-text search'
  },
  
  messagingLayer: {
    messageQueue: 'RabbitMQ/SQS',
    eventStream: 'Kafka for event sourcing'
  }
};

4. Detailed Design

// API Design
class UserAPI {
  // POST /api/users/register
  async register(req, res) {
    const { email, password, name } = req.body;
    
    // Validate input
    if (!this.validateEmail(email)) {
      return res.status(400).json({ error: 'Invalid email' });
    }
    
    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);
    
    // Create user
    const user = await db.users.create({
      email,
      password: hashedPassword,
      name,
      createdAt: new Date()
    });
    
    // Generate token
    const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
    
    return res.status(201).json({ user, token });
  }
  
  // GET /api/users/:id
  async getUser(req, res) {
    const { id } = req.params;
    
    // Try cache first
    const cached = await redis.get(`user:${id}`);
    if (cached) {
      return res.json(JSON.parse(cached));
    }
    
    // Load from database
    const user = await db.users.findById(id);
    
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    
    // Cache for 5 minutes
    await redis.setEx(`user:${id}`, 300, JSON.stringify(user));
    
    return res.json(user);
  }
}

Common System Design Patterns

1. Client-Server Architecture

┌─────────┐         ┌─────────┐         ┌──────────┐
│ Client  │────────>│  Server │────────>│ Database │
│ (Web)   │<────────│  (API)  │<────────│          │
└─────────┘         └─────────┘         └──────────┘

2. Three-Tier Architecture

┌──────────────┐
│ Presentation │  (Web/Mobile)
└──────────────┘

┌──────────────┐
│   Business   │  (Application Logic)
└──────────────┘

┌──────────────┐
│     Data     │  (Database)
└──────────────┘

3. Microservices Architecture

                  ┌──────────────┐
                  │ API Gateway  │
                  └──────────────┘

        ┌────────────────┼────────────────┐
        │                │                │
   ┌────────┐      ┌────────┐      ┌────────┐
   │ User   │      │ Post   │      │ Feed   │
   │Service │      │Service │      │Service │
   └────────┘      └────────┘      └────────┘
        │                │                │
   ┌────────┐      ┌────────┐      ┌────────┐
   │User DB │      │Post DB │      │Feed DB │
   └────────┘      └────────┘      └────────┘

Trade-offs in System Design

const designTradeoffs = {
  consistencyVsAvailability: {
    strongConsistency: 'Slower, but always correct data',
    eventualConsistency: 'Faster, but temporary inconsistencies',
    example: 'Banking (strong) vs Social media (eventual)'
  },
  
  latencyVsThroughput: {
    lowLatency: 'Fast response times, fewer concurrent requests',
    highThroughput: 'More requests handled, higher latency',
    example: 'Real-time gaming (latency) vs Batch processing (throughput)'
  },
  
  normalizationVsDenormalization: {
    normalized: 'Less storage, more joins, slower reads',
    denormalized: 'More storage, faster reads, complex writes',
    example: 'OLTP (normalized) vs OLAP (denormalized)'
  },
  
  verticalVsHorizontalScaling: {
    vertical: 'Simpler, limited by hardware, single point of failure',
    horizontal: 'Complex, unlimited scaling, distributed challenges',
    example: 'Small apps (vertical) vs Large apps (horizontal)'
  }
};

System Design Interview Approach

Step 1: Clarify Requirements

const questionsToAsk = [
  'What are the core features?',
  'How many users do we expect?',
  'What is the read/write ratio?',
  'What are the latency requirements?',
  'What is the expected growth rate?',
  'Are there any specific constraints?',
  'What is the budget?'
];

Step 2: Back-of-the-Envelope Calculations

// Example: Design Twitter
const twitterEstimation = {
  users: {
    total: 500_000_000,
    dailyActive: 200_000_000,
    tweetsPerDay: 500_000_000
  },
  
  storage: {
    tweetSize: 280, // characters
    avgTweetSizeBytes: 280 * 2, // UTF-8
    mediaPercentage: 0.2,
    avgMediaSize: 200 * 1024, // 200KB
    
    dailyStorage() {
      const textStorage = this.tweetsPerDay * this.avgTweetSizeBytes;
      const mediaStorage = this.tweetsPerDay * this.mediaPercentage * this.avgMediaSize;
      return (textStorage + mediaStorage) / (1024 ** 3); // GB
    }
  },
  
  bandwidth: {
    tweetsPerSecond: 500_000_000 / 86400, // ~5,787 tweets/sec
    peakMultiplier: 3,
    peakTweetsPerSecond() {
      return this.tweetsPerSecond * this.peakMultiplier;
    }
  }
};

console.log(`Daily storage: ${twitterEstimation.storage.dailyStorage().toFixed(2)} GB`);
console.log(`Peak tweets/sec: ${twitterEstimation.bandwidth.peakTweetsPerSecond().toFixed(0)}`);

Step 3: Design Core Components

// Example: URL Shortener
class URLShortenerDesign {
  components = {
    // API Layer
    api: {
      endpoints: [
        'POST /api/shorten - Create short URL',
        'GET /:shortCode - Redirect to original URL',
        'GET /api/stats/:shortCode - Get analytics'
      ]
    },
    
    // Storage
    database: {
      type: 'NoSQL (DynamoDB/MongoDB)',
      schema: {
        shortCode: 'string (primary key)',
        originalURL: 'string',
        userId: 'string',
        createdAt: 'timestamp',
        expiresAt: 'timestamp',
        clicks: 'number'
      },
      indexes: ['userId', 'createdAt']
    },
    
    // Caching
    cache: {
      type: 'Redis',
      strategy: 'Cache popular URLs',
      ttl: '1 hour'
    },
    
    // URL Generation
    generator: {
      algorithm: 'Base62 encoding',
      length: '7 characters',
      collisionHandling: 'Retry with different seed'
    }
  };
  
  generateShortCode(id) {
    const base62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    let shortCode = '';
    
    while (id > 0) {
      shortCode = base62[id % 62] + shortCode;
      id = Math.floor(id / 62);
    }
    
    return shortCode.padStart(7, '0');
  }
}

.NET System Design Example

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;

public class SystemDesignExample
{
    private readonly IDistributedCache _cache;
    private readonly IUserRepository _userRepository;
    
    public SystemDesignExample(
        IDistributedCache cache,
        IUserRepository userRepository)
    {
        _cache = cache;
        _userRepository = userRepository;
    }
    
    [HttpGet("users/{id}")]
    public async Task<IActionResult> GetUser(string id)
    {
        // Try cache first
        var cacheKey = $"user:{id}";
        var cached = await _cache.GetStringAsync(cacheKey);
        
        if (!string.IsNullOrEmpty(cached))
        {
            return Ok(JsonSerializer.Deserialize<User>(cached));
        }
        
        // Load from database
        var user = await _userRepository.GetByIdAsync(id);
        
        if (user == null)
        {
            return NotFound();
        }
        
        // Cache for 5 minutes
        var cacheOptions = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        };
        
        await _cache.SetStringAsync(
            cacheKey,
            JsonSerializer.Serialize(user),
            cacheOptions
        );
        
        return Ok(user);
    }
}

Key Principles

const designPrinciples = [
  'Keep it simple (KISS)',
  'Don\'t repeat yourself (DRY)',
  'Separation of concerns',
  'Single responsibility principle',
  'Design for failure',
  'Optimize for common cases',
  'Measure before optimizing',
  'Plan for growth',
  'Security by design',
  'Document your decisions'
];

Interview Tips

  • Start with requirements: Always clarify functional and non-functional requirements
  • Think out loud: Explain your thought process
  • Consider trade-offs: Discuss pros and cons of different approaches
  • Use diagrams: Draw architecture diagrams
  • Estimate capacity: Show back-of-the-envelope calculations
  • Identify bottlenecks: Point out potential issues
  • Discuss alternatives: Show you know multiple solutions

Summary

System design is about creating scalable, reliable, and maintainable systems. Start by gathering requirements, estimate capacity, design high-level architecture, then dive into detailed design. Consider trade-offs between consistency, availability, latency, and throughput. Use proven patterns like client-server, three-tier, and microservices. Always think about scalability, reliability, and performance. Essential for building production systems and acing system design interviews.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your System-design Knowledge

Ready to put your skills to the test? Take our interactive System-design quiz and get instant feedback on your answers.