0Pricing
DSA Interview Prep · Lezione

Il framework per i colloqui di system design

Segua i cinque passaggi del framework RADIO (Requirements, API, Data, Infrastructure, Optimise) e si eserciti ad applicarlo a un URL shortener.

Il framework per i colloqui di system design è una lezione DSA Interview Prep gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

Perché la progettazione dei sistemi è importante nei colloqui

I colloqui di progettazione dei sistemi verificano la capacità di ragionare su larga scala: come progetterebbe Twitter, YouTube o un servizio di abbreviazione degli URL per miliardi di utenti? A differenza dei problemi di coding, che hanno un'unica risposta corretta, la progettazione dei sistemi è aperta: deve individuare e motivare i compromessi. Nei ruoli senior e staff, questa fase occupa esclusivamente 30–45 minuti.

Gli intervistatori valutano se sa chiarire i requisiti, stimare il carico, proporre un'architettura di alto livello, approfondire i componenti principali e discutere i compromessi, comunicando sempre con chiarezza. Un framework strutturato evita divagazioni e garantisce la trattazione di tutte le dimensioni fondamentali.

# System design is not about a single correct answer.
# Interviewers look for:
eval_criteria = [
    'Ability to clarify requirements before designing',
    'Back-of-envelope capacity estimation',
    'High-level architecture with clear components',
    'Data modelling and storage choice',
    'Handling scalability (10x, 100x load)',
    'Trade-off discussion (consistency vs availability, etc.)',
    'Communication: talking through decisions as you make them',
]
for c in eval_criteria:
    print('-', c)

Panoramica del framework RADIO

Il framework RADIO fornisce una struttura ripetibile in 5 passaggi per qualsiasi colloquio di progettazione dei sistemi:

  • R — Requisiti: funzionali e non funzionali
  • A — Progettazione delle API: quali operazioni espone il sistema?
  • D — Modello dei dati: quali dati vengono memorizzati e in che modo?
  • I — Infrastruttura: componenti di alto livello (server, code, cache)
  • O — Ottimizzazione: colli di bottiglia, caching, sharding, replicazione

Segua sempre questi passaggi nell'ordine indicato, ma torni ai passaggi precedenti e li perfezioni quando emergono nuove informazioni. Dedichi all'incirca lo stesso tempo a ogni fase. Non inizi mai a disegnare i componenti senza aver prima chiarito i requisiti.

RADIO = {
    'R': 'Requirements — What must the system do? What scale?',
    'A': 'API         — Define endpoints/operations the system exposes',
    'D': 'Data Model  — Entities, schemas, storage types',
    'I': 'Infra       — High-level architecture: servers, queues, caches, CDN',
    'O': 'Optimise    — Identify and address bottlenecks, trade-offs',
}
for step, desc in RADIO.items():
    print(f'[{step}] {desc}')

print('\nTiming guide for a 45-min interview:')
print('  R: 5 min | A: 5 min | D: 10 min | I: 15 min | O: 10 min')

Passo R: chiarire i requisiti

Non inizi mai la progettazione senza aver chiarito i requisiti. Chieda informazioni sui requisiti funzionali (che cosa fa il sistema) e sui requisiti non funzionali (scala, latenza, disponibilità). Per un servizio di abbreviazione degli URL:

  • Funzionali: abbreviare un URL, reindirizzare all'URL originale, supportare facoltativamente alias personalizzati e scadenze
  • Non funzionali: quanti URL al giorno? Prevalgono le letture o le scritture? Qual è il requisito di disponibilità (99.9% o 99.99%)? Qual è la latenza accettabile?

Esplicitare le proprie ipotesi dimostra maturità. Spesso gli intervistatori forniscono specifiche volutamente vaghe per verificare se pone le domande giuste. Due minuti di chiarimenti evitano di progettare il sistema sbagliato.

# Requirements questions for URL Shortener:
functional = [
    'Shorten a given URL to a 7-character alias',
    'Redirect short URL to original URL',
    'Allow custom aliases (optional)',
    'URL expiry (optional)',
    'Analytics: click count per URL (optional)',
]
non_functional = [
    '100 million new URLs per day (write: ~1160/sec)',
    '10:1 read:write ratio => 11,600 redirects/sec',
    'Redirects must be < 100ms p99 latency',
    '99.99% availability (< 1 hr downtime/year)',
    'URLs must be globally accessible',
]
print('Functional:')
for f in functional: print(' -', f)
print('\nNon-functional:')
for nf in non_functional: print(' -', nf)

Passo R: stima preliminare

Dopo aver definito i requisiti, stimi la capacità. Questo dimostra che sa ragionare sulla scala prima di proporre soluzioni. I numeri chiave da ricavare sono: richieste al secondo (RPS), spazio di archiviazione necessario al giorno o all'anno, larghezza di banda e memoria per il caching.

Usi numeri tondi e faccia liberamente stime approssimative. Agli intervistatori interessa l'ordine di grandezza, non la precisione dei valori. Esempio per un servizio di abbreviazione degli URL: 100M scritture/giorno ÷ 86400 ≈ 1160 scritture/sec. 10B letture/giorno ÷ 86400 ≈ 115K letture/sec. Ogni record URL ≈ 500 byte: 100M × 500B = 50 GB/giorno, 18 TB/anno.

# Back-of-envelope for URL Shortener
writes_per_day = 100_000_000        # 100 million URLs/day
read_write_ratio = 100              # 100:1 read/write
reads_per_day = writes_per_day * read_write_ratio
bytes_per_url = 500                 # url string + metadata
years_to_store = 5

print('=== Capacity Estimation ===')
print(f'Writes/sec:  {writes_per_day / 86400:.0f}')
print(f'Reads/sec:   {reads_per_day / 86400:,.0f}')
print(f'Storage/day: {writes_per_day * bytes_per_url / 1e9:.1f} GB')
print(f'Storage total ({years_to_store}y): {writes_per_day * bytes_per_url * 365 * years_to_store / 1e12:.1f} TB')

cache_hit_rate = 0.80
hot_urls = reads_per_day * (1 - cache_hit_rate)
print(f'\n80% cache hit rate: {cache_hit_rate*100}% of reads from cache')
print(f'DB reads/sec: {hot_urls / 86400:,.0f}')

Passo A: progettazione delle API

Definisca la superficie delle API, ovvero le operazioni che il sistema espone ai client e ai servizi interni. Specifichi chiaramente il metodo HTTP, il percorso dell'endpoint, i parametri della richiesta e il formato della risposta. Questo costituisce la base per il resto della progettazione: tutto il resto serve a implementare queste API.

Per un servizio di abbreviazione degli URL, le due API principali sono: (1) POST /shorten per creare un URL breve, (2) GET /{alias} per eseguire il reindirizzamento. Facoltativamente: DELETE /{alias} per eliminare, GET /{alias}/stats per le analisi. Specifichi i codici di risposta (201 Created, 301 Redirect, 404 Not Found).

# API Design for URL Shortener
apis = [
    {
        'method': 'POST',
        'path': '/api/v1/shorten',
        'request': '{"long_url": "https://...", "alias": "optional", "expires_at": "optional"}',
        'response': '201 Created: {"short_url": "https://short.ly/abc1234", "alias": "abc1234"}',
    },
    {
        'method': 'GET',
        'path': '/{alias}',
        'request': 'No body',
        'response': '301 Redirect to long_url (or 404 Not Found)',
    },
    {
        'method': 'GET',
        'path': '/api/v1/{alias}/stats',
        'request': 'Optional: date range query params',
        'response': '200 OK: {"clicks": 42000, "unique_visitors": 15000}',
    },
]
for api in apis:
    print(f'{api["method"]} {api["path"]}')
    print(f'  Request:  {api["request"]}')
    print(f'  Response: {api["response"]}')
    print()

Passo D: modello dei dati

Il modello dei dati definisce quali dati vengono memorizzati e in che modo. Identifichi le entità principali e i relativi attributi. Per un servizio di abbreviazione degli URL: una tabella urls con alias (chiave primaria), long_url, created_at, expires_at e user_id. Una tabella clicks facoltativa per le analisi.

La scelta del tipo di archiviazione corretto è fondamentale: un DB relazionale per dati strutturati con query complesse; un archivio chiave-valore (Redis, DynamoDB) per ricerche O(1) degli alias su larga scala; un object store (S3) per blob di grandi dimensioni. Per un servizio di abbreviazione degli URL, un archivio chiave-valore indicizzato per alias è ideale per le letture, affiancato da un DB relazionale per le scritture e la gestione.

# Data model for URL Shortener

# Core table (PostgreSQL)
urls_schema = '''
CREATE TABLE urls (
    alias       VARCHAR(16) PRIMARY KEY,   -- e.g., 'abc1234'
    long_url    TEXT NOT NULL,
    user_id     UUID REFERENCES users(id),
    created_at  TIMESTAMP DEFAULT NOW(),
    expires_at  TIMESTAMP,
    click_count BIGINT DEFAULT 0
);
CREATE INDEX ON urls(user_id);
'''

# Cache layer (Redis) for hot reads
redis_schema = '''
alias  =>  long_url       # O(1) GET on cache hit
TTL = 24 hours (or until expiry)
Cache eviction: LRU
'''

print('PostgreSQL schema:')
print(urls_schema)
print('Redis cache:')
print(redis_schema)
print('Storage split: Redis for hot reads (~80%), PostgreSQL for writes and cold reads')

Passo I: infrastruttura di alto livello

Tracci l'infrastruttura di alto livello: quali server gestiscono le diverse responsabilità, come fluiscono i dati tra i componenti e quali servizi esterni vengono utilizzati. Per un servizio di abbreviazione degli URL su larga scala:

  • Bilanciatore del carico: distribuisce il traffico tra le repliche dei servizi di scrittura e lettura
  • Servizio di scrittura: genera l'alias, ne verifica l'unicità, scrive nel DB e invalida la cache
  • Servizio di lettura/reindirizzamento: verifica prima la cache Redis e, in caso di cache miss, ricorre al DB
  • DB relazionale: fonte autorevole (con repliche di lettura)
  • Cluster Redis: memorizza nella cache le associazioni URL più richieste per letture inferiori al millisecondo
# ASCII architecture sketch
architecture = '''
         Clients
            |
       [Load Balancer]
       /             \\
  [Write API]    [Read/Redirect API]
      |                  |  |
  [Alias Gen]       [Redis Cache]
      |                  |
  [PostgreSQL]---->[Read Replicas]

Alias Generation:
- Base62 encoding of auto-incrementing ID
- 62^7 = 3.5 trillion unique URLs (enough for 5 years at 100M/day)
- OR random 7-char Base62 with collision check
'''
print(architecture)
print('Key design decisions:')
print('  - Read/Write split: separate services for scalability')
print('  - Cache-aside pattern: read from Redis, fallback to DB')
print('  - 301 vs 302 redirect: 301 cached by browser (less load), 302 always hits server (analytics)')

Passo O: ottimizzazione e gestione dei colli di bottiglia

La fase di ottimizzazione affronta i colli di bottiglia e consente di scalare il sistema. Per un servizio di abbreviazione degli URL, le principali problematiche sono: la latenza dei reindirizzamenti (avvicinare Redis agli utenti tramite CDN o cache edge), l'unicità degli alias su larga scala (usare un server centrale di ticket oppure un hash con rilevamento delle collisioni) e il collo di bottiglia delle scritture nel DB (scritture in batch o asincrone tramite una coda).

Discuta esplicitamente i compromessi: i reindirizzamenti 301 riducono il carico sul server ma compromettono la precisione delle analisi; i reindirizzamenti 302 sono tracciabili ma aggiungono latenza. Una memorizzazione nella cache con scadenza lunga riduce il carico sul DB, ma rischia di mantenere URL obsoleti. Dimostrare di comprendere queste tensioni è indice di una mentalità da professionista senior.

# Key optimisation discussion points
optimisations = {
    'Read latency': [
        'Redis cluster in multiple regions (CDN-edge caching)',
        '301 redirect for non-tracked URLs (client caches)',
        '302 redirect when click analytics are needed',
    ],
    'Write throughput': [
        'Async writes: accept request, enqueue to Kafka, batch-commit to DB',
        'Ticket server (centralised ID generator) to avoid UUID collision',
        'Or: hash(user_id + timestamp + random) with retry on collision',
    ],
    'Availability': [
        'Multi-AZ PostgreSQL with automatic failover',
        'Redis Sentinel or Redis Cluster for HA cache',
        'Health checks + circuit breaker on each service',
    ],
    'Storage': [
        'Partition urls table by hash(alias) for horizontal scaling',
        'Archive expired URLs to cold storage (S3)',
    ],
}
for area, points in optimisations.items():
    print(f'{area}:')
    for p in points: print(f'  - {p}')
    print()

Generazione degli alias: codifica Base62

Un dettaglio tecnico fondamentale dei servizi di abbreviazione degli URL è come generare alias brevi e univoci. L'approccio standard consiste nell'usare un ID intero autoincrementante del database e codificarlo in Base62 (cifre da 0 a 9, lettere da a a z e da A a Z). Una stringa Base62 di 7 caratteri può rappresentare 62^7 ≈ 3,5 trilioni di URL univoci, sufficienti per decenni al ritmo di 100M al giorno.

Questo approccio non produce collisioni, perché ogni ID è univoco, e genera stringhe brevi e sicure per gli URL. L'associazione ID→alias è deterministica e reversibile. Il compromesso è che gli ID sequenziali producono alias prevedibili, con conseguenti problemi di sicurezza. Rimescoli l'alfabeto Base62 oppure usi un offset del contatore per ridurre la prevedibilità.

import string

BASE62_CHARS = string.digits + string.ascii_lowercase + string.ascii_uppercase
BASE = 62

def encode_base62(num):
    if num == 0:
        return BASE62_CHARS[0]
    result = ''
    while num:
        result = BASE62_CHARS[num % BASE] + result
        num //= BASE
    return result

def decode_base62(s):
    result = 0
    for c in s:
        result = result * BASE + BASE62_CHARS.index(c)
    return result

# Generate aliases for IDs 1, 100, 1000, 10^9
for id_ in [1, 100, 1000, 1_000_000, 10**9, 3_521_614_606207]:
    alias = encode_base62(id_)
    decoded = decode_base62(alias)
    print(f'ID {id_:20,} => alias "{alias}" (len={len(alias)}) => decoded={decoded}')

Applicazione di RADIO alla progettazione del feed di Twitter

Applichiamo brevemente RADIO alla progettazione di un feed di notizie simile a Twitter per mostrare che il framework è generalizzabile:

  • R: gli utenti pubblicano tweet, seguono altri utenti e visualizzano un feed dei tweet degli account seguiti. Scala: 300M utenti, 500M tweet/giorno; il feed deve caricarsi in <2s.
  • A: POST /tweets, GET /feed, GET /timeline/{user_id}
  • D: tabella tweets (id, user_id, content, created_at); tabella follows (follower_id, followee_id); cache del feed per ogni utente
  • I: il servizio di fan-out scrive i nuovi tweet nei feed dei follower (precalcolati); Cassandra per le tabelle follow/tweet con molte scritture; Redis per le cache dei feed
# Fan-out on Write vs Fan-out on Read trade-off
fan_out_strategies = {
    'Fan-out on Write (Push)': {
        'How': 'When user A posts, immediately write to all followers feeds',
        'Pro': 'O(1) feed read — feed is precomputed',
        'Con': 'Celebrities with 10M followers => 10M writes per post; very slow write',
        'Best for': 'Users with few followers (regular users)',
    },
    'Fan-out on Read (Pull)': {
        'How': 'When user reads feed, query followees tweets and merge',
        'Pro': 'Writes are fast (one DB write per tweet)',
        'Con': 'Feed read is slow: must query all N followees',
        'Best for': 'Celebrity accounts (few reads per post)',
    },
    'Hybrid': {
        'How': 'Push for regular users, pull for celebrities',
        'Pro': 'Balances read and write cost',
        'Con': 'More complex implementation',
        'Best for': 'Production systems like Twitter',
    },
}
for strategy, details in fan_out_strategies.items():
    print(f'{strategy}:')
    for k, v in details.items(): print(f'  {k}: {v}')
    print()

Errori comuni nei colloqui di system design

Eviti questi errori frequenti che possono compromettere i colloqui di system design:

  • Passare subito alle soluzioni: disegnare riquadri prima di aver chiarito i requisiti segnala cattive pratiche ingegneristiche
  • Nessuna stima: progettare senza conoscere la scala equivale a procedere per tentativi
  • Overengineering: progettare per 1 miliardo di utenti quando la traccia ne indica 10.000 fa perdere tempo nel colloquio
  • Nessun compromesso: ogni scelta ha vantaggi e svantaggi; non menzionarli suggerisce una comprensione superficiale
  • Silenzio: gli intervistatori devono sentire il suo processo di ragionamento; esponga le decisioni man mano che le prende
# Checklist: before you stop talking, verify you covered:
checklist = [
    '[ ] Asked clarifying questions about scale and constraints',
    '[ ] Made capacity estimates (RPS, storage, bandwidth)',
    '[ ] Defined the API surface clearly',
    '[ ] Described the data model and storage choices',
    '[ ] Drew a high-level architecture with named components',
    '[ ] Identified the main bottleneck and proposed a solution',
    '[ ] Discussed at least one trade-off explicitly',
    '[ ] Verified the design meets the stated requirements',
]
for item in checklist:
    print(item)

Verifica rapida

Metta alla prova la sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep affrontati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato: il framework RADIO struttura i colloqui di system design in Requirements, API, Data Model, Infrastructure e Optimise, è sempre necessario chiarire i requisiti funzionali e non funzionali e stimare la capacità prima di proporre un progetto e discutere esplicitamente i compromessi — ogni scelta progettuale ha vantaggi e svantaggi che gli intervistatori si aspettano che lei sappia illustrare. Nella prossima lezione esploreremo come scegliere tra sistemi di storage SQL e NoSQL in base ai pattern di accesso, alla consistenza e alla scala.

Domande Frequenti

La lezione «Il framework per i colloqui di system design» è gratuita?

Sì — il testo completo di «Il framework per i colloqui di system design» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Il framework per i colloqui di system design»?

Segua i cinque passaggi del framework RADIO (Requirements, API, Data, Infrastructure, Optimise) e si eserciti ad applicarlo a un URL shortener. Eserciti DSA Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Il framework per i colloqui di system design»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DSA Interview Prep?

Sì. Ogni lezione DSA Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Il framework per i colloqui di system design
  2. Archiviazione scalabile dei dati: SQL e NoSQL
  3. Caching, CDN e bilanciamento del carico
  4. Progettare un rate limiter e un feed di Twitter
← Torna a DSA Interview Prep