InterviewHack.ai
Start free
Blog/Preguntas de entrevista de diseño de sistemas distribuidos (35+)

Preguntas de entrevista de diseño de sistemas distribuidos (35+)

16 de septiembre de 2026

system-designbackend

Complete system design interview article in Spanish rioplatense with 40 questions, detailed answers, real code

Introducción

Vas a una entrevista de sistema distribuido y te preguntan: "Diseñá un sistema de mensajería para 100 millones de usuarios". El silencio que sigue puede costar el trabajo.

Las entrevistas de system design son las que más candidatos eliminan en FAANG, unicornios y empresas que pagan en dólares — no porque sean imposibles, sino porque nadie explica cómo estructurar el pensamiento. Este artículo cambia eso.

Cubrimos 35+ preguntas reales organizadas por categoría, con respuestas detalladas, código real en Python/Go/pseudo-código, y los trade-offs que los entrevistadores realmente esperan que menciones.


Cómo funcionan estas entrevistas (antes de las preguntas)

Antes de memorizar respuestas, entendé el juego. El entrevistador no busca la respuesta correcta — busca cómo pensás bajo presión.

El framework que funciona:

  • Clarificá el scope (5 min): usuarios, escala, latencia aceptable, consistencia requerida
  • Estimá los números (3 min): QPS, storage, ancho de banda
  • Diseñá el happy path (10 min): arquitectura de alto nivel
  • Deep dive en componentes críticos (15 min): el entrevistador te va a pedir que profundices en uno
  • Identificá y discutí bottlenecks (5 min): single points of failure, hotspots

Fundamentos: preguntas que siempre vuelven

1. ¿Qué es un sistema distribuido y cuáles son sus principales desafíos?

Un sistema distribuido es un conjunto de nodos independientes que colaboran para el usuario final como si fuera uno solo. Los desafíos son:

  • Network partitions: los nodos se desconectan entre sí. No podés asumir que un mensaje llegó.
  • Latencia no determinística: un llamado que tarda 5 ms hoy puede tardar 2 segundos mañana.
  • Consistencia parcial: dos nodos pueden tener versiones diferentes del mismo dato al mismo tiempo.
  • Fallos silenciosos: un nodo puede caerse sin avisar.

La respuesta que distingue a los candidatos buenos: no solo nombran estos problemas, sino que explican cómo los sistemas concretos los mitigan (idempotencia, heartbeats, reintento con backoff exponencial).

2. Explicá el Teorema CAP

El teorema CAP dice que en presencia de una partición de red (P), un sistema distribuido solo puede garantizar consistencia (C) o disponibilidad (A), no ambas simultáneamente.

  • CP (Consistency + Partition tolerance): el sistema rechaza requests si no puede garantizar datos actualizados. Ejemplo: HBase, ZooKeeper.
  • AP (Availability + Partition tolerance): el sistema responde siempre, aunque con datos posiblemente desactualizados. Ejemplo: Cassandra, DynamoDB en modo eventual.

La trampa de la entrevista: CAP asume que una partición ya ocurrió. En la práctica, elegís tu trade-off en diseño, no solo durante la falla. La mayoría de los sistemas son AP con consistencia eventual configurable.

Lo que el entrevistador quiere oir: "Para este caso particular, elijo AP porque los usuarios prefieren ver datos ligeramente desactualizados a recibir un error." Eso es pensamiento de producto + sistemas.

3. ¿Qué es consistencia eventual y cuándo la usás?

Consistencia eventual significa que si dejás de escribir en un sistema, todos los nodos eventualmente van a converger al mismo valor. No hay garantía de cuándo, pero eventualmente ocurre.

La usás cuando:

  • El costo del conflicto es bajo (likes en un post — si ves 1.204 o 1.205 no importa)
  • La disponibilidad es prioritaria
  • La partición de red es una posibilidad real

Ejemplo concreto: el contador de views de YouTube. No necesitás que sea exacto al segundo. DNS también usa consistencia eventual — actualizás un registro y tarda horas en propagarse, y está bien.

Cuando NO la usás: inventario de ecommerce (no podés vender el mismo producto dos veces), saldos bancarios, seats de vuelos.

4. ¿Cómo diferenciás latencia de throughput?

  • Latencia: tiempo de un request individual (p50, p95, p99). "Este endpoint tarda 45 ms en el p99."
  • Throughput: cuántos requests puede manejar el sistema por unidad de tiempo. "El servicio procesa 50,000 RPS."

Son independientes. Un sistema puede tener alta latencia y alto throughput (batch processing), o baja latencia y bajo throughput (una Raspberry Pi respondiendo queries simples).

En la entrevista: siempre preguntá cuál optimizar. "¿Qué es más crítico para este feature: latencia p99 < 100ms, o procesar 1M eventos/hora?"

5. ¿Qué es un single point of failure y cómo lo eliminás?

Un SPOF es cualquier componente cuya caída tumba el sistema completo.

Estrategias de eliminación:

Antes (SPOF):
[Client] → [Single DB] → Error si la DB cae

Después (sin SPOF):
[Client] → [Load Balancer] → [App Server 1]  ┐
                          → [App Server 2]  ├─ → [DB Primary]
                          → [App Server N]  ┘      ↓ replication
                                              [DB Replica 1]
                                              [DB Replica 2]

Además del hardware: dependency injection para tests, circuit breakers para servicios externos, y health checks automáticos con reinicio.


Diseño de bases de datos y almacenamiento

6. ¿Cuándo usás SQL vs NoSQL?

No es una guerra religiosa — es un trade-off:

Usá SQL cuando:

  • Los datos tienen estructura fija y relaciones complejas (JOINs necesarios)
  • Necesitás transacciones ACID (órdenes, pagos)
  • El esquema no va a cambiar mucho

Usá NoSQL cuando:

  • Escala horizontal masiva (sharding nativo)
  • Modelo de datos flexible o jerárquico (documentos, grafos)
  • Latencia ultra-baja con acceso por key
  • Event sourcing o time-series

Ejemplo de respuesta buena en entrevista: "Para los perfiles de usuario usaría PostgreSQL por las relaciones y ACID. Para el feed de actividad usaría Cassandra — es append-only, necesita alta disponibilidad, y los queries son siempre por user_id con orden temporal."

7. ¿Cómo diseñás el sharding de una base de datos?

Sharding es partir los datos horizontalmente entre múltiples nodos. Tres estrategias:

Range sharding: shard 1 tiene users 1-1M, shard 2 tiene 1M-2M. Simple pero crea hotspots si los últimos registros son los más activos.

Hash sharding: shard = hash(user_id) % num_shards. Distribución uniforme, pero un JOIN cross-shard es costoso.

Directory sharding: una tabla de lookup mapea cada clave a su shard. Flexibilidad total, pero el directory se convierte en un SPOF.

python
# Hash sharding básico
def get_shard(user_id: int, num_shards: int) -> int:
    return hash(str(user_id)) % num_shards

# Consistent hashing (mejor para rebalanceo)
import hashlib

def consistent_hash(key: str, nodes: list[str]) -> str:
    ring = sorted((hashlib.md5(n.encode()).hexdigest(), n) for n in nodes)
    h = hashlib.md5(key.encode()).hexdigest()
    for node_hash, node in ring:
        if h <= node_hash:
            return node
    return ring[0][1]  # wrap around

El problema que mencionás para diferenciarte: hotspot con celebrities o eventos virales. Un usuario con 50M followers genera 100x más tráfico que el promedio. Solución: sharding por contenido + read replicas para cuentas calientes.

8. ¿Qué es consistent hashing y por qué importa?

Cuando agregás o sacás un nodo en hash sharding tradicional, tenés que remapear casi todas las claves (num_keys / num_nodes no alcanza — en la práctica es mucho más). Con consistent hashing, solo remapeás las claves del nodo afectado.

El truco: poner los nodos en un "ring" de hashes. Cada key se asigna al nodo más cercano en sentido horario. Cuando sacás un nodo, sus keys van al siguiente nodo del ring.

Usado en: Amazon DynamoDB, Apache Cassandra, Memcached (ketama), CDNs.

9. Diseñá un sistema de caché distribuida

Las preguntas clásicas dentro de este tema:

¿Qué estrategia de caché usás?

  • Cache-aside (lazy loading): la aplicación consulta el caché, si hay miss va a la DB y llena el caché. Simple pero el primer request siempre es lento.
  • Write-through: cada write va a caché y DB simultáneamente. Consistencia fuerte, pero writes más lentos.
  • Write-back: el write va solo al caché; la DB se actualiza asíncronamente. Riesgoso si el nodo cae antes de persistir.

¿Qué evictás cuando el caché está lleno?

  • LRU (Least Recently Used): el más usado en la práctica. Redis lo implementa nativamente.
  • LFU (Least Frequently Used): mejor para patrones estables, peor para patrones cambiantes.
  • TTL fijo: simple, predecible. Bueno para datos con vida útil conocida (tokens, sesiones).
python
# LRU Cache en Python (entrevista clásica también)
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity: int):
        self.cache = OrderedDict()
        self.capacity = capacity

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)
        return self.cache[key]

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)

Cache stampede: cuando el caché expira y miles de requests van a la DB al mismo tiempo. Solución: mutex lock en el primer request, o probabilistic early expiration.

10. ¿Qué es un índice de base de datos y cuándo no lo usás?

Un índice acelera las lecturas a cambio de más espacio y writes más lentos (el índice también se actualiza con cada insert/update).

No usás un índice cuando:

  • La tabla es pequeña (el full scan es igual o más rápido)
  • La columna tiene baja cardinalidad (booleano, status con 3 valores) — el índice apenas ayuda
  • La tabla tiene muchos más writes que reads
  • Ya tenés demasiados índices y los writes se volvieron el bottleneck

Un error común en entrevistas: decir "le pongo índice a todo para que sea rápido." Eso es una señal de alarma. Un senior sabe que cada índice tiene costo.


Comunicación y mensajería

11. ¿Cuándo usás comunicación síncrona vs asíncrona?

Síncrona (REST/gRPC): cuando necesitás la respuesta inmediata para continuar. Login, checkout, cualquier query que el usuario espera.

Asíncrona (colas, eventos): cuando el procesamiento puede ocurrir después sin impactar la experiencia del usuario. Envío de emails, generación de reportes, procesamiento de imágenes, notificaciones push.

Regla práctica: si el usuario ve un spinner mientras espera, es síncrono. Si el usuario sigue navegando y "te avisamos cuando esté listo", es asíncrono.

12. ¿Cómo diseñás un sistema de colas de mensajes?

Un message queue desacopla productores de consumidores. Las componentes:

  • Producer: publica mensajes
  • Queue/Topic: almacena mensajes temporalmente
  • Consumer: procesa mensajes, a su propio ritmo
  • Dead letter queue (DLQ): donde van los mensajes que fallaron N veces
[Order Service] → [queue: orders_created] → [Inventory Service]
                                          → [Email Service]
                                          → [Analytics Service]

Preguntas de seguimiento típicas:

¿Cómo garantizás que un mensaje se procesa exactamente una vez? R: es muy difícil. En la práctica garantizás "at least once" y hacés los consumidores idempotentes (si procesan el mismo mensaje dos veces, el resultado es el mismo).

¿Cómo ordenás mensajes? R: Kafka particiona por key y garantiza orden dentro de una partición. Si necesitás orden global, una sola partición, pero perdés throughput.

13. ¿Kafka vs RabbitMQ: cuándo usás cada uno?

Kafka:

  • Event streaming a gran escala (millones de eventos/segundo)
  • Retención de mensajes larga (podés "rebobinar" y reprocesar)
  • Múltiples consumers independientes (cada uno lleva su propio offset)
  • Casos: analytics en tiempo real, event sourcing, pipelines de datos

RabbitMQ:

  • Task queues donde el mensaje se elimina al ser procesado
  • Routing complejo (exchanges, bindings, topics)
  • Casos: background jobs, notificaciones, sistemas de tareas

La respuesta que el entrevistador espera: no "usaría Kafka porque es mejor", sino "para este caso específico donde necesito múltiples consumers independientes que puedan reprocesar eventos, Kafka es la elección correcta. Si solo necesito descargar trabajo a workers, RabbitMQ es más simple."

14. ¿Qué es un circuit breaker y cuándo lo implementás?

Un circuit breaker detecta cuándo un servicio downstream está fallando y deja de llamarlo temporalmente, en lugar de acumular requests que van a fallar igual.

Estados:

  • Closed (normal): los requests pasan
  • Open (servicio caído): los requests fallan inmediatamente sin llegar al servicio
  • Half-open (probando): deja pasar algunos requests para ver si el servicio se recuperó
python
import time
from enum import Enum

class State(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=30):
        self.state = State.CLOSED
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.last_failure_time = None

    def call(self, func, *args, **kwargs):
        if self.state == State.OPEN:
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = State.HALF_OPEN
            else:
                raise Exception("Circuit breaker OPEN — fast fail")

        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise e

    def _on_success(self):
        self.failure_count = 0
        self.state = State.CLOSED

    def _on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = State.OPEN

Diseños clásicos de sistemas

15. Diseñá un URL shortener (tipo bit.ly)

Este es el más común en entrevistas de nivel medio. El entrevistador mide si sabés estimar escala y elegir el almacenamiento correcto.

Estimación:

  • 100M URLs nuevas por día = ~1,150 writes/segundo
  • 10:1 read/write ratio = ~11,500 reads/segundo
  • URL corta: ~7 chars. 100M * 7 bytes = ~700 MB/día de storage

Generación del short code:

python
import hashlib
import base64

def shorten(long_url: str) -> str:
    # MD5 del URL → tomar primeros 6 chars del base64
    digest = hashlib.md5(long_url.encode()).digest()
    code = base64.urlsafe_b64encode(digest)[:6].decode()
    return f"https://short.ly/{code}"

# Problema: colisiones. Mejor: auto-increment + base62 encoding
def to_base62(n: int) -> str:
    chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    result = []
    while n:
        result.append(chars[n % 62])
        n //= 62
    return "".join(reversed(result)) or "0"

Arquitectura:

  • Un ID autoincremental en la DB (o un servicio de ID distribuido como Twitter Snowflake)
  • Convierte el ID a base62 → ese es el short code
  • El redirect: caché (Redis) primero, DB si hay miss → HTTP 301 (permanente) o 302 (temporal para analytics)

Trade-off que mencionás: 301 vs 302. Con 301, el browser cachea el redirect y no volvés a ver esa visita en analytics. Con 302, cada visita pasa por tu servidor — mejor para métricas, peor para latencia.

16. Diseñá Twitter/X (el feed)

La pregunta que más diferencia seniors de midlevels. El core challenge: fanout.

Cuando Lady Gaga (150M followers) postea, ¿cómo hacés que 150 millones de usuarios vean ese tweet en su feed?

Fanout on write (push model):

Post tweet → escribir en los 150M timelines inmediatamente

Pro: el read del feed es O(1). Con: el write de una celebrity puede durar horas.

Fanout on read (pull model):

Read feed → buscar tweets de cada usuario que seguís → mergear

Pro: el write es O(1). Con: el read es O(n_following) — costoso para usuarios que siguen a 5,000 personas.

La solución híbrida que usan sistemas reales:

  • Para usuarios normales: fanout on write (la mayoría tiene < 5,000 followers, el write es rápido)
  • Para cuentas con > N followers (celebrities): fanout on read
  • El feed del usuario = precomputado (regular users) + on-demand (celebrities que sigue)
python
def get_feed(user_id: int, page: int) -> list[Tweet]:
    # 1. Tweets precomputados en cache del usuario
    cached_tweets = redis.lrange(f"feed:{user_id}", page*20, page*20+19)

    # 2. Celebrities que sigue → fetch on-demand
    celebrity_ids = get_followed_celebrities(user_id)
    celeb_tweets = [get_recent_tweets(cid, limit=20) for cid in celebrity_ids]

    # 3. Merge y ordenar por timestamp
    all_tweets = cached_tweets + [t for tweets in celeb_tweets for t in tweets]
    return sorted(all_tweets, key=lambda t: t.timestamp, reverse=True)[:20]

17. Diseñá un sistema de notificaciones push

Componentes:

  1. 1Notification Service: recibe el evento y lo enruta
  2. 2User Preference Service: si el usuario tiene notificaciones activadas, qué canal (push, email, SMS)
  3. 3Template Service: renderiza el mensaje
  4. 4Delivery Workers: integración con APNs (iOS), FCM (Android), SMTP, Twilio

El punto clave de diseño: rate limiting por usuario. Si un usuario recibe 500 notificaciones por minuto (bug en el sistema), tiene que haber un throttle. También: deduplication — si el mismo evento se publica dos veces (retry de la cola), no querés mandar la notificación dos veces.

python
class NotificationDeduplicator:
    def __init__(self, redis_client, ttl_seconds=3600):
        self.redis = redis_client
        self.ttl = ttl_seconds

    def is_duplicate(self, notification_id: str) -> bool:
        key = f"notif:seen:{notification_id}"
        # SET key 1 NX EX ttl — atómico en Redis
        result = self.redis.set(key, 1, nx=True, ex=self.ttl)
        return result is None  # None = ya existía = duplicado

18. Diseñá un sistema de rate limiting

Un rate limiter protege tu API de abuso y asegura fair use.

Algoritmos principales:

Token Bucket: tenés un bucket con N tokens. Cada request consume 1 token. Los tokens se recargan a tasa fija. Permite bursts hasta el tamaño del bucket.

Sliding Window Log: guardás el timestamp de cada request en una ventana deslizante. Si hay más de N en los últimos X segundos, rechazás. Preciso pero costoso en memoria.

Fixed Window Counter: contador simple por ventana de tiempo (por minuto, por hora). Problema: un usuario puede hacer 2x requests en el boundary de dos ventanas.

python
# Token Bucket con Redis
import time
import redis

def is_allowed(user_id: str, max_tokens: int, refill_rate: float) -> bool:
    r = redis.Redis()
    now = time.time()
    key = f"ratelimit:{user_id}"

    pipe = r.pipeline()
    pipe.hgetall(key)
    results = pipe.execute()

    data = results[0]
    if not data:
        tokens = max_tokens - 1
        last_refill = now
    else:
        last_refill = float(data[b'last_refill'])
        tokens = float(data[b'tokens'])
        # Recargar tokens basado en tiempo transcurrido
        elapsed = now - last_refill
        tokens = min(max_tokens, tokens + elapsed * refill_rate)
        last_refill = now
        tokens -= 1

    if tokens < 0:
        return False  # Rate limited

    pipe = r.pipeline()
    pipe.hmset(key, {'tokens': tokens, 'last_refill': last_refill})
    pipe.expire(key, 3600)
    pipe.execute()
    return True

Distributed rate limiting: con múltiples API servers, cada uno tiene su propio contador local → inconsistencia. Solución: Redis centralizado como fuente de verdad, o algoritmos probabilísticos (cada nodo hace una estimación y rechaza si la estimación supera el límite).

19. Diseñá un sistema de búsqueda con autocompletar

El challenge: para cada keystroke del usuario, responder en < 100ms con sugerencias relevantes.

Estructura de datos: Trie (prefix tree). Cada nodo representa un caracter. El camino desde la raíz hasta un nodo es el prefijo buscado.

python
class TrieNode:
    def __init__(self):
        self.children: dict[str, TrieNode] = {}
        self.is_end = False
        self.frequency = 0  # para rankear por popularidad

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str, frequency: int = 1):
        node = self.root
        for char in word.lower():
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
            node.frequency = max(node.frequency, frequency)
        node.is_end = True

    def search_prefix(self, prefix: str, limit: int = 10) -> list[str]:
        node = self.root
        for char in prefix.lower():
            if char not in node.children:
                return []
            node = node.children[char]
        return self._dfs(node, prefix, limit)

    def _dfs(self, node: TrieNode, current: str, limit: int) -> list[str]:
        results = []
        if node.is_end:
            results.append((node.frequency, current))
        for char, child in node.children.items():
            results.extend(self._dfs(child, current + char, limit))
        results.sort(reverse=True)
        return [word for _, word in results[:limit]]

En producción: el Trie en memoria se precalcula offline. Para escala: particionás por primera letra (26 shards), cacheas los prefijos más comunes (top 1M queries), y actualizás el Trie con un batch job diario (no en tiempo real).

20. Diseñá un sistema de video streaming (tipo YouTube)

Las piezas clave:

Upload pipeline:

[Usuario sube video] → [Upload Service] → [Object Storage (S3)]
                    → [Queue: video_uploaded]
                    → [Transcoding Workers] → [múltiples resoluciones: 360p, 720p, 1080p, 4K]
                    → [CDN distribution]

Adaptive Bitrate Streaming (ABR): el cliente no pide "el video", pide segmentos de 2-4 segundos. Detecta el bandwidth disponible y pide la resolución apropiada para cada segmento. Protocolo: HLS (Apple) o DASH.

El punto de diseño que diferencia candidatos: el CDN. No servís el video desde tu origin server — lo cacheás en edge nodes alrededor del mundo. La primera vez que alguien en Buenos Aires ve un video popular, el edge node lo descarga del origin y lo cachea. Todos los siguientes lo sirven desde el edge.

Deduplication de videos: si dos usuarios suben el mismo video (contenido idéntico), ¿guardás dos copias? Podés usar un hash del contenido (SHA-256 del archivo) para detectar duplicados. YouTube hace esto para detectar copyrights también.


Consistencia y transacciones

21. ¿Qué es una transacción distribuida y cómo la manejás?

Una transacción distribuida involucra múltiples servicios o bases de datos que deben commitear o rollback juntos.

2-Phase Commit (2PC):

  • Fase 1 (Prepare): el coordinador pregunta a todos los participantes si pueden commitear
  • Fase 2 (Commit/Abort): si todos dijeron sí, el coordinador manda commit; si alguno dijo no, manda abort

Problema: el coordinador es un SPOF. Si se cae entre la fase 1 y la 2, los participantes quedan bloqueados.

Saga pattern (el que se usa en microservices):

Order Service: crea orden (estado: PENDING)
    → Payment Service: cobra (ok)
        → Inventory Service: descuenta stock (ok)
            → Shipping Service: crea envío (FALLA)
                ← Compensating transaction: devuelve stock
                ← Compensating transaction: reembolsa pago
                ← Order Service: cancela orden

Cada paso tiene una transacción compensatoria. No hay rollback atómico, pero eventualmente el sistema llega a un estado consistente.

22. ¿Qué es idempotencia y por qué es crítica?

Una operación es idempotente si ejecutarla una o N veces produce el mismo resultado.

En sistemas distribuidos, los retries son inevitables (timeouts, fallos de red). Si tu operación no es idempotente, un retry puede cobrar dos veces, crear dos órdenes, o enviar dos emails.

python
# No idempotente - peligroso
def charge_user(user_id: str, amount: float):
    db.execute("INSERT INTO charges (user_id, amount) VALUES (?, ?)", user_id, amount)

# Idempotente con idempotency key
def charge_user(user_id: str, amount: float, idempotency_key: str):
    existing = db.execute(
        "SELECT id FROM charges WHERE idempotency_key = ?",
        idempotency_key
    ).fetchone()
    if existing:
        return existing['id']  # Ya procesado, devolvé el resultado anterior

    return db.execute(
        "INSERT INTO charges (user_id, amount, idempotency_key) VALUES (?, ?, ?)",
        user_id, amount, idempotency_key
    ).lastrowid

Stripe usa esto para todos sus endpoints de pago. El cliente genera un UUID por intento de cobro y lo manda como header Idempotency-Key.

23. ¿Cómo implementás distributed locking?

Cuando múltiples instancias de un servicio intentan acceder al mismo recurso, necesitás un lock distribuido.

Con Redis (Redlock):

python
import redis
import uuid
import time

class DistributedLock:
    def __init__(self, redis_client, key: str, ttl_ms: int = 5000):
        self.r = redis_client
        self.key = f"lock:{key}"
        self.ttl = ttl_ms
        self.token = str(uuid.uuid4())

    def acquire(self, timeout_ms: int = 1000) -> bool:
        deadline = time.time() + timeout_ms / 1000
        while time.time() < deadline:
            # SET key token NX PX ttl — atómico
            acquired = self.r.set(self.key, self.token, nx=True, px=self.ttl)
            if acquired:
                return True
            time.sleep(0.01)
        return False

    def release(self):
        # Solo liberar si el token es el nuestro (evitar liberar el lock de otro)
        script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        self.r.eval(script, 1, self.key, self.token)

Problema del Redlock con un solo nodo: si el nodo Redis se cae justo después de adquirir el lock, el lock desaparece y otro proceso puede adquirirlo antes de que el TTL expire. Para producción seria: usar los 5 nodos Redis del algoritmo Redlock oficial, o mejor aún, ZooKeeper/etcd.


Preguntas de diseño avanzado

24. Diseñá un sistema de geolocalización (tipo Uber)

El challenge central: encontrar todos los drivers dentro de X km del rider en tiempo real.

Naive approach: para cada request de rider, calcular la distancia a todos los drivers. Con 1M drivers activos: inviable.

GeoHash: convierte coordenadas (lat, lng) en un string alfanumérico. Puntos cercanos tienen prefijos similares.

python
# GeoHash simplificado — en producción usás una librería
import geohash2  # pip install geohash2

def find_nearby_drivers(lat: float, lng: float, radius_km: float) -> list[str]:
    # Precision 6 = ~1.2km de precisión
    center_hash = geohash2.encode(lat, lng, precision=6)

    # GeoHash vecinos (9 celdas: centro + 8 adyacentes)
    neighbors = geohash2.neighbors(center_hash) + [center_hash]

    # Query Redis con SMEMBERS por cada celda
    driver_ids = []
    for cell in neighbors:
        ids = redis.smembers(f"drivers:geo:{cell}")
        driver_ids.extend(ids)

    return driver_ids

def update_driver_location(driver_id: str, lat: float, lng: float):
    new_hash = geohash2.encode(lat, lng, precision=6)
    old_hash = redis.get(f"driver:{driver_id}:geohash")

    if old_hash != new_hash:
        if old_hash:
            redis.srem(f"drivers:geo:{old_hash}", driver_id)
        redis.sadd(f"drivers:geo:{new_hash}", driver_id)
        redis.set(f"driver:{driver_id}:geohash", new_hash)

En producción: Redis tiene GEOADD / GEODIST / GEORADIUS nativos. Pero entender GeoHash demuestra que conocés la fundamentación.

25. Diseñá un sistema de recomendaciones

El challenge: para cada usuario, recomendar ítems que probablemente le gusten sin haberlos visto.

Collaborative Filtering (User-Based):

  • Encontrá usuarios similares a ti (mismos likes)
  • Recomendá lo que ellos vieron y vos no

Collaborative Filtering (Item-Based):

  • Encontrá ítems similares a los que te gustaron
  • Netflix lo usa: "porque viste X, te puede gustar Y"

Matrix Factorization (lo que usan los sistemas grandes):

  • Descomponés la matriz user×item en dos matrices latentes de menor dimensión
  • Cada usuario y cada ítem quedan representados como vectores en ese espacio latente
  • La recomendación es el producto punto: score(user, item) = user_vec · item_vec

El punto de escala que importa en la entrevista: computar recomendaciones en tiempo real para millones de usuarios es imposible. Se precomputan offline (batch job diario/semanal) y se cachean. Para "tiempo real" solo actualizás basado en la última sesión.

26. Diseñá un sistema de búsqueda distribuida (tipo Elasticsearch)

Los componentes:

Inverted Index: la estructura de datos core. Para cada término, guardás la lista de documentos que lo contienen.

"python" → [doc_1, doc_4, doc_7, doc_23]
"interview" → [doc_1, doc_2, doc_11]
"python interview" → intersección → [doc_1]

Sharding de índices: particionás el índice entre múltiples nodos. Cada query se manda a todos los shards en paralelo, y los resultados se mergean y re-rankean.

Relevance scoring: TF-IDF (frecuencia del término en el doc × inverso de la frecuencia en el corpus) o BM25.

El punto avanzado: near-real-time indexing. Elasticsearch mantiene un buffer en memoria, lo escribe a segmentos inmutables cada segundo (refresh), y luego los mergea (merge = compactación). Los documentos son buscables ~1 segundo después de ser indexados.

27. Diseñá un sistema de pagos

Es la entrevista con más foco en correctness que en escala.

Propiedades críticas:

  • Idempotencia: un pago no puede ejecutarse dos veces
  • Atomicidad: el débito y el crédito son una sola operación
  • Auditoría completa: cada estado del pago se loguea con timestamp

State machine de un pago:

CREATED → PROCESSING → AUTHORIZED → CAPTURED → SETTLED
                    ↓           ↓
                 DECLINED    REFUNDED
                    ↓
                 FAILED

Nunca actualizás el estado en-place. Cada transición es un INSERT nuevo en la tabla de eventos (event sourcing). El estado actual es el último evento.

Doble-entry bookkeeping (cómo los sistemas reales evitan bugs de balance):

sql
-- Cada transacción genera dos filas (debit + credit)
INSERT INTO ledger (account_id, amount, transaction_id, entry_type)
VALUES
  ('acc_buyer',  -100.00, 'txn_123', 'DEBIT'),
  ('acc_seller', +100.00, 'txn_123', 'CREDIT');

-- La suma de todos los entries siempre debe ser cero
SELECT SUM(amount) FROM ledger; -- debe ser 0

28. Diseñá un sistema de logs y monitoreo

Los tres pilares del observability:

Logs: eventos discretos. "El usuario X hizo login a las 14:32:05."

Metrics: series de tiempo numéricas. "CPU al 78%, 1,230 RPS."

Traces: el camino completo de un request a través de múltiples servicios.

Pipeline típico:

[Service] → [Log Agent (Fluentd/Filebeat)] → [Queue (Kafka)]
         → [Stream Processor (Flink/Spark)] → [TimeSeries DB (InfluxDB/Prometheus)]
                                           → [Search Engine (Elasticsearch)]
                                           → [Alerting (PagerDuty)]

El sampling problem: si logueas cada request a 100,000 RPS, tu pipeline de logs consume más recursos que tu aplicación. Estrategia: sample al 1% para traces normales, 100% para errores, y hot paths configurables.

29. Diseñá un sistema de archivos distribuido (tipo Google Drive)

Split en chunks: los archivos se dividen en bloques de 4-64 MB. Cada bloque se replica en N nodos (típicamente 3).

Ventajas:

  • Si un nodo cae, los otros 2 tienen el bloque
  • Podés descargar partes del archivo en paralelo desde distintos nodos
  • Deduplicación por bloque (dos archivos que comparten el mismo bloque no duplican storage)

Metadata service: un servicio separado que sabe qué bloques forman cada archivo, en qué nodos están, y el orden. Es el directorio. Se replica con alta disponibilidad.

Sync client (el challenge real de Dropbox/Drive):

  • Detectar cambios locales: inotify en Linux, FSEvents en macOS, ReadDirectoryChangesW en Windows
  • Delta sync: no re-subís el archivo entero, solo los bloques que cambiaron
  • Conflict resolution: dos usuarios editaron el mismo archivo simultáneamente → crea una copia de conflicto (la estrategia más segura)

Preguntas sobre disponibilidad y reliability

30. ¿Cómo diseñás para alta disponibilidad?

Los números concretos que tenés que conocer:

| Disponibilidad | Downtime por año |

|---|---|

| 99% | 3.65 días |

| 99.9% | 8.76 horas |

| 99.99% | 52.6 minutos |

| 99.999% | 5.26 minutos |

Técnicas:

  • Redundancia activa: múltiples instancias procesando en paralelo, el fallo de una es transparente
  • Redundancia pasiva (standby): una instancia primaria, una de backup que toma el control si la primaria falla (failover)
  • Health checks: load balancers que dejan de enviar tráfico a instancias unhealthy
  • Multi-region: replica en al menos dos regiones geográficas para sobrevivir a una caída de datacenter

Lo que el entrevistador quiere ver: que entiendas que la disponibilidad tiene un costo. 99.999% requiere infraestructura multi-region, runbooks de failover, chaos engineering regular, y un equipo de oncall. No siempre vale la pena.

31. ¿Qué es backpressure y cómo lo manejás?

Backpressure ocurre cuando el producer genera datos más rápido de lo que el consumer puede procesar.

Producer: 10,000 eventos/segundo
Consumer: puede procesar 3,000 eventos/segundo
Cola: se llena → empieza a dropear eventos o bloquear al producer

Estrategias:

  • Drop: descartás eventos bajo carga. Aceptable para métricas no críticas.
  • Slow down producer: el consumer le señala al producer que frene (protocols como gRPC streaming lo soportan nativo).
  • Scale consumers: agregar más instancias de consumer en tiempo real (Kubernetes Horizontal Pod Autoscaler basado en profundidad de la cola).
  • Load shedding: rechazás requests cuando el sistema está sobrecargado. Es mejor responder 503 que responder 20 segundos después.

32. Explicá el concepto de "thundering herd" y cómo lo mitigás

Thundering herd: cuando un evento hace que muchos procesos o nodos accedan simultáneamente al mismo recurso.

Ejemplo 1: tu caché expira y 10,000 requests concurrentes van a la DB.

Ejemplo 2: el servidor de DNS se cae, se recupera, y miles de clientes intentan conectarse al mismo tiempo.

Mitigaciones:

  • Mutex/semaphore en el caché: solo el primer thread que detecta el miss va a la DB; los demás esperan el resultado (también llamado "request coalescing").
  • Probabilistic early expiration: en lugar de expirar exactamente a tiempo, empezás a refrescar el caché antes de que expire, con probabilidad creciente. Nunca todo expira junto.
  • Jitter en los retries: en lugar de reintentar exactamente cada N segundos, agregás aleatoriedad. sleep(base * 2^attempt + random(0, base)).

33. ¿Cómo diseñás un sistema de health checks y alertas?

Liveness check: ¿el proceso sigue vivo? Un endpoint /health que responde 200 OK. Kubernetes lo usa para decidir si reiniciar el pod.

Readiness check: ¿el servicio está listo para recibir tráfico? Verifica dependencias (DB conectada, caché disponible). El load balancer lo usa para decidir si enviar tráfico.

python
from fastapi import FastAPI
import redis
import psycopg2

app = FastAPI()

@app.get("/health/live")
def liveness():
    return {"status": "ok"}

@app.get("/health/ready")
def readiness():
    checks = {}
    try:
        redis.Redis().ping()
        checks["redis"] = "ok"
    except Exception as e:
        checks["redis"] = f"error: {e}"

    try:
        conn = psycopg2.connect(DATABASE_URL)
        conn.close()
        checks["db"] = "ok"
    except Exception as e:
        checks["db"] = f"error: {e}"

    all_ok = all(v == "ok" for v in checks.values())
    return {"status": "ok" if all_ok else "degraded", "checks": checks}

Alerting: la señal más confiable no son los logs, son las métricas de negocio. Si las órdenes por minuto caen 50% en 5 minutos, algo está roto aunque todos los servicios reporten "healthy".


Preguntas sobre seguridad y compliance

34. ¿Cómo diseñás autenticación y autorización en microservices?

Autenticación (quién sos): JWT tokens emitidos por un Auth Service central. Cada servicio valida el JWT localmente sin llamar al Auth Service en cada request.

python
import jwt
from datetime import datetime, timedelta

SECRET = "your-256-bit-secret"

def create_token(user_id: str, roles: list[str]) -> str:
    payload = {
        "sub": user_id,
        "roles": roles,
        "iat": datetime.utcnow(),
        "exp": datetime.utcnow() + timedelta(hours=1)
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")

def verify_token(token: str) -> dict:
    return jwt.decode(token, SECRET, algorithms=["HS256"])
    # Lanza jwt.ExpiredSignatureError si expiró
    # Lanza jwt.InvalidTokenError si fue manipulado

Autorización (qué podés hacer): RBAC (Role-Based) es el standard. Cada endpoint verifica si el rol del usuario incluye el permiso necesario.

El punto de madurez en la entrevista: mencionar que los JWTs tienen una limitación — no podés revocarlos antes de que expiren (son stateless). Solución: access tokens de vida corta (15 min) + refresh tokens en DB (revocables). O un token blacklist en Redis para revocación urgente.

35. ¿Cómo protegés datos sensibles en un sistema distribuido?

Encryption at rest: los datos en disco están encriptados. AWS, GCP, Azure lo hacen transparent con sus storage services. Para PII especialmente sensible: field-level encryption (encriptás el campo antes de persistir, la DB no ve el plaintext).

Encryption in transit: TLS 1.3 entre todos los servicios. En microservices: mutual TLS (mTLS) donde ambas partes se autentican, no solo el cliente.

Tokenization: reemplazás el dato sensible (número de tarjeta) con un token random. El token vive en tus sistemas; el dato real vive solo en un PCI-DSS compliant vault. Stripe hace esto — vos solo guardás el pm_xxx token.

Data minimization: no guardés datos que no necesitás. Si el usuario te dio su dirección para hacer el envío y ya se envió, ¿necesitás seguir guardándola?


Preguntas de estimación (Fermi)

36. ¿Cuánto storage necesita WhatsApp para los mensajes de un día?

Este tipo de pregunta mide si podés pensar en órdenes de magnitud sin calculadora.

Proceso:

  • WhatsApp: ~100B usuarios activos, ~65B mensajes/día
  • Tamaño promedio de mensaje de texto: ~100 bytes
  • 65B mensajes × 100 bytes = 6.5 TB de texto/día
  • ~15% de mensajes son media (fotos, videos, audio)
  • Foto promedio comprimida: ~100 KB; 10B mensajes media × 100 KB = ~1 PB/día
  • Total: ~1 PB/día de storage nuevo (dominado por media, no texto)

Lo que el entrevistador evalúa: no el número exacto, sino el proceso. Descomponés el problema, estimás cada componente, identificás qué domina.

37. ¿Cuántos servidores necesita Google Search?

  • Búsquedas globales: ~8.5B/día ≈ ~100,000 búsquedas/segundo
  • Una búsqueda involucra: crawling (datos ya indexados), ranking (CPU intensivo), serving (respuesta)
  • Un servidor moderno puede manejar ~1,000-5,000 requests/segundo de serving
  • Solo para serving: 100,000 / 2,000 = ~50 servidores (parecería poco pero recordá CDN + caché de resultados populares)
  • Index servers, crawl servers, ranking servers: multiplicate por 10-100x
  • Con replicación, geografía, spare capacity: ~1M servidores es razonable (Google publicó ~2M en 2020)

Conceptos que siempre caen al final

38. ¿Qué es un vector clock y cuándo lo usás?

Un vector clock es un mecanismo para rastrear causalidad en sistemas distribuidos sin sincronizar relojes físicos.

Nodo A: [A:1, B:0, C:0]  ← A hace un evento
Nodo B: [A:1, B:1, C:0]  ← B recibe el evento de A y hace uno propio
Nodo C: [A:1, B:1, C:1]  ← C recibe de B y hace uno propio

Si dos eventos tienen vector clocks que no son comparables, hay un conflicto (ocurrieron concurrentemente). Amazon DynamoDB usó vector clocks para detectar conflictos y presentarle al cliente las versiones en conflicto.

39. ¿Qué es el problema del Two Generals?

Es la prueba de que no existe ningún protocolo que garantice acuerdo entre dos partes sobre una acción futura a través de un canal no confiable.

Relevancia práctica: muestra por qué los ACKs/ACKs-de-ACKs no terminan nunca — siempre hay incertidumbre. En la práctica lo resolvemos con timeouts + idempotencia + retries, aceptando que el sistema puede estar en estados inconsistentes brevemente.

40. ¿Qué es CRDTs y cuándo los usás?

CRDT (Conflict-free Replicated Data Type): estructuras de datos diseñadas para que múltiples réplicas puedan actualizarse independientemente y mergearse sin conflictos.

Ejemplo: G-Counter (Grow-only Counter). Cada nodo mantiene su propio contador. El valor global es la suma de todos. No podés decrementar (por eso "grow-only"), pero podés tener N réplicas incrementando sin coordinación y el merge es trivial.

Usado en: Figma (colaboración en tiempo real), Redis CRDT (módulo enterprise), Riak.

La limitación: no todos los tipos de datos tienen un CRDT natural. Para operaciones que requieren semántica de "último gana" o "primer gana", seguís necesitando coordinación.


Cómo usar este material en tu entrevista

Memorizar respuestas es la estrategia incorrecta. Lo que funciona:

1. Practicá el framework de clarificación: antes de diseñar nada, hacé 3-5 preguntas que demuestren que pensás en el problema de negocio, no solo en el técnico.

2. Hablá los trade-offs en voz alta: "Podría usar X, que tiene la ventaja de A y B, pero el costo de C. Dada la escala que mencionaste, voy con Y porque..." Eso es lo que separa senior de midlevel.

3. Usá números concretos: "Asumiendo 1M usuarios activos con 10 requests promedio por día, son ~116 RPS, lo cual un solo servidor maneja con comodidad. A 100M usuarios, necesito escala horizontal."

4. Identificá el componente más interesante: en cada diseño hay un cuello de botella o un trade-off que domina todos los demás. Encontralo y profundizá ahí — el entrevistador lo va a hacer de todas formas.

5. Usá InterviewHack.ai para practicar con preguntas adaptadas a la empresa real donde aplicás — investigamos quién te va a entrevistar y qué sistemas usan, para que tu práctica sea específica, no genérica.


Recursos para profundizar

  • "Designing Data-Intensive Applications" (Martin Kleppmann) — el libro que todos los que entrevistan para Staff+ leyeron
  • System Design Primer (GitHub) — gratuito, cubre los patrones esenciales
  • AWS/Google Cloud whitepapers — lee cómo los sistemas reales resolvieron los problemas que estudiaste
  • Engineering blogs: Stripe, Cloudflare, Discord, Figma — todos publican posts técnicos de sus decisiones de arquitectura

FAQ

¿Cuántas preguntas de system design caen en una entrevista real?+

Típicamente una sola pregunta de diseño que ocupa 45-60 minutos. El entrevistador arranca con un enunciado amplio (diseñá Twitter, diseñá un rate limiter) y va profundizando en componentes específicos. La clave es manejar el tiempo: 5 min de clarificación, 10 min de diseño general, 25 min de deep dive en 1-2 componentes críticos.

¿Qué lenguaje de programación debo usar para el código en system design?+

El que te pidan o el que mejor domines. Python es el más común por su legibilidad. Lo importante no es la sintaxis perfecta sino demostrar que entendés la lógica. En system design, el pseudocódigo es perfectamente aceptable — nadie espera que compiles en vivo.

¿Cómo estimo la escala si no sé los números de la empresa?+

Siempre preguntá: '¿Cuántos usuarios activos diarios esperamos?' y '¿Cuál es el ratio read/write?' Si no saben o es hipotético, asumí en voz alta: 'Voy a asumir 10M DAU con 100 requests/usuario/día = 1,155 RPS.' Demostrar el proceso es más valioso que tener el número exacto.

¿Cuál es la diferencia entre una entrevista de system design para mid-level vs senior?+

Para mid-level: que puedas diseñar un sistema funcional con los componentes correctos. Para senior: que identifiques los trade-offs correctos antes de que el entrevistador los señale, que hables de operabilidad (monitoreo, deploys, rollbacks), y que puedas discutir por qué descartaste alternativas razonables. La profundidad y la proactividad son lo que distingue.

¿Necesito memorizar todos los patrones de system design?+

No memorizar — entender. Si entendés por qué existe el consistent hashing (el rebalanceo de shards), vas a poder derivar cómo funciona en el momento. Los patrones que sí conviene tener fluidos: CAP theorem, sharding strategies, cache patterns (LRU, write-through), message queues, y el fanout problem. Son los que vuelven en el 80% de las entrevistas.

¿Cómo preparo una entrevista de system design en 2 semanas?+

Semana 1: dominá los fundamentos (CAP, sharding, caché, colas). Diseñá 3 sistemas clásicos (URL shortener, feed de Twitter, rate limiter) en papel. Semana 2: practicá en voz alta con un timer de 45 min. Grabate o practicá con alguien. Después de cada sesión, buscá el engineering blog del sistema que diseñaste y compará. El libro de Kleppmann cubre todo el fundamento teórico.

Related articles

Cómo usar el método STAR en entrevistas (con ejemplos reales)

Aprende a responder preguntas difíciles usando el método STAR en entrevistas. Consejos y ejemplos concretos para roles remotos tech de LATAM.

Cómo conseguir trabajo remoto en dólares desde LATAM: guía real

Descubre consejos concretos para conseguir trabajo remoto en dólares desde LATAM: estrategias de búsqueda, preparación y entrevista para roles tecnológicos.

Las mejores preguntas para hacerle al entrevistador al final

Descubre las mejores preguntas para hacerle al entrevistador al final, útiles para entrevistas tech remotas, diferenciándote y logrando roles en dólares.

Cómo preparar entrevistas de desarrollo sin experiencia previa

Consejos prácticos para enfrentar entrevistas de tu primer trabajo como desarrollador, incluso sin experiencia. Técnicas para destacar y convencer en cada etapa.

Prepare for your real interview

Paste your job link: we research who's interviewing you and rehearse you live.

Start free →

Have an interview coming up? Install the live copilot →

InterviewHack.ai

Prepare for the exact interview: who's interviewing you, a tailored CV, and a real coach.

Product

JobsFree ATS checkerInterview-English checkSalary checkLATAM salary reportFree coursesBlogTailored CVSpoken practiceIt's free

Remote jobs

ReactPythonFull-StackLATAMArgentinaMexicoSee all →

Prepare

Spoken practiceFrontendBackendAI EngineerBy companySell with your CV

Company

For employersAboutContactPrivacyTerms

© 2026 InterviewHack.ai · Your CV is yours. Never used to train anything. · A product of IA-PTY