Topic Overview

QUIC Protocol

Master QUIC protocol: UDP-based, encrypted, multiplexed transport for HTTP/3. Understand connection migration, zero-RTT, and improvements over TCP.

QUIC (Quick UDP Internet Connections) is a transport protocol developed by Google that runs over UDP. It's designed to reduce latency and improve performance compared to TCP, and serves as the foundation for HTTP/3.


What is QUIC?

QUIC provides:

  • UDP-based: Runs over UDP (not TCP)
  • Built-in encryption: TLS 1.3 integrated
  • Multiplexing: Multiple streams over single connection
  • Connection migration: Survives IP address changes
  • Faster handshake: 0-RTT or 1-RTT (vs 2-RTT for TCP+TLS)

Benefits over TCP:

  • No head-of-line blocking: Streams are independent
  • Faster connection: 0-RTT or 1-RTT handshake
  • Better mobile: Handles network changes better
  • Built-in security: Encryption by default

QUIC vs TCP

TCP Limitations

1. Head-of-Line Blocking

TCP Connection:
  Stream 1: [packet lost] → Blocks Stream 2, 3, 4...
  
All streams blocked until lost packet retransmitted

2. Slow Handshake

TCP: 3-way handshake (1.5 RTT)
TLS: 2-RTT handshake
Total: 3.5 RTT before data

3. Connection Migration

TCP: Connection tied to IP address
IP change: Connection breaks, must reconnect

QUIC Improvements

1. No Head-of-Line Blocking

QUIC Connection:
  Stream 1: [packet lost] → Only Stream 1 blocked
  Stream 2, 3, 4: Continue independently

2. Faster Handshake

QUIC: 0-RTT or 1-RTT handshake
TLS 1.3 integrated
Faster connection establishment

3. Connection Migration

QUIC: Connection survives IP changes
Connection ID: Identifies connection (not IP)
IP change: Connection continues

QUIC Features

1. Multiplexing

Multiple streams over single connection:

QUIC Connection:
  Stream 1: Request 1 → Response 1
  Stream 2: Request 2 → Response 2
  Stream 3: Request 3 → Response 3
  
All streams independent (no blocking)

2. Built-in Encryption

TLS 1.3 integrated:

QUIC Packet:
  [Header (encrypted)]
  [Payload (encrypted)]
  
No separate TLS handshake
Encryption by default

3. Connection Migration

Survives IP changes:

Connection ID: 0x1234
IP 1: 192.168.1.100
  → Network change
IP 2: 10.0.0.50
  
Connection continues (same Connection ID)

4. 0-RTT and 1-RTT

0-RTT (Resumed connection):

Client → Server: Data (with encrypted handshake)
Server → Client: Response

No round trip for handshake

1-RTT (New connection):

Client → Server: Initial (handshake)
Server → Client: Response (handshake complete)
Client → Server: Data

One round trip for handshake

QUIC Packet Structure

QUIC Packet:
  [Header]
    - Connection ID
    - Packet Number
    - Flags
  [Payload]
    - Stream Data
    - ACK
    - Crypto (TLS)

Key fields:

  • Connection ID: Identifies connection (not IP)
  • Packet Number: For ordering and reliability
  • Stream ID: Identifies stream
  • Encrypted: Header and payload encrypted

Examples

QUIC Connection

# QUIC client (conceptual)
import aioquic

async def quic_client():
    # Create QUIC connection
    connection = await aioquic.connect(
        host='example.com',
        port=443,
        configuration=aioquic.QuicConfiguration(
            is_client=True,
            alpn_protocols=['h3']  # HTTP/3
        )
    )
    
    # Open stream
    stream = await connection.create_stream()
    
    # Send data (0-RTT or 1-RTT)
    await stream.send(b'GET / HTTP/3\r\nHost: example.com\r\n\r\n')
    
    # Receive data
    data = await stream.receive()
    print(data)
    
    connection.close()

HTTP/3 over QUIC

# HTTP/3 uses QUIC
import httpx

async def http3_client():
    async with httpx.AsyncClient(http3=True) as client:
        # HTTP/3 request (uses QUIC)
        response = await client.get('https://example.com')
        print(response.text)

Connection Migration

class QUICConnection:
    def __init__(self, connection_id):
        self.connection_id = connection_id
        self.ip_address = None
    
    async def migrate(self, new_ip):
        """Migrate connection to new IP"""
        # Connection ID stays same
        # Update IP address
        self.ip_address = new_ip
        
        # Send migration packet
        await self.send_migration_packet(new_ip)
        
        # Connection continues with new IP

Common Pitfalls

  • UDP blocking: Some firewalls block UDP. Fix: Ensure UDP port 443 is open
  • Not understanding connection migration: Assuming connection tied to IP. Fix: Use Connection ID, not IP
  • Expecting TCP semantics: QUIC is different from TCP. Fix: Understand QUIC's stream model
  • Not handling 0-RTT: Missing optimization. Fix: Implement 0-RTT for resumed connections
  • NAT traversal: UDP NAT issues. Fix: Use STUN/TURN if needed

Interview Questions

Beginner

Q: What is QUIC and how does it differ from TCP?

A:

QUIC (Quick UDP Internet Connections) is a transport protocol over UDP designed to improve upon TCP.

Differences:

FeatureTCPQUIC
TransportTCPUDP
EncryptionSeparate (TLS)Built-in (TLS 1.3)
Handshake3-RTT (TCP + TLS)0-RTT or 1-RTT
Head-of-line blockingYes (all streams)No (streams independent)
Connection migrationNo (tied to IP)Yes (Connection ID)

Key improvements:

  • No head-of-line blocking: Streams independent
  • Faster handshake: 0-RTT or 1-RTT
  • Built-in encryption: TLS 1.3 integrated
  • Connection migration: Survives IP changes

Example:

TCP: Stream 1 packet lost → All streams blocked
QUIC: Stream 1 packet lost → Only Stream 1 blocked

Intermediate

Q: Explain how QUIC eliminates head-of-line blocking and enables connection migration.

A:

Head-of-Line Blocking Elimination:

TCP Problem:

TCP Connection (single stream):
  Packet 1, 2, 3, 4, 5...
  Packet 2 lost → Blocks 3, 4, 5...
  
All data blocked until Packet 2 retransmitted

QUIC Solution:

QUIC Connection (multiple streams):
  Stream 1: Packet 1, 2, 3...
  Stream 2: Packet 1, 2, 3...
  Stream 3: Packet 1, 2, 3...
  
Stream 1 Packet 2 lost → Only Stream 1 blocked
Stream 2, 3 continue independently

How it works:

  • Multiple streams: Each stream has independent sequence numbers
  • Independent retransmission: Lost packet in one stream doesn't block others
  • UDP-based: No TCP ordering constraints

Connection Migration:

TCP Problem:

TCP Connection:
  Tied to: (Source IP, Source Port, Dest IP, Dest Port)
  IP change: Connection breaks, must reconnect

QUIC Solution:

QUIC Connection:
  Identified by: Connection ID (not IP)
  IP change: Connection continues (same Connection ID)

How it works:

  1. Connection ID: Unique identifier for connection
  2. IP change: Client sends packet with new IP, same Connection ID
  3. Server recognizes: Same Connection ID = same connection
  4. Connection continues: No reconnection needed

Example:

Mobile device:
  WiFi: 192.168.1.100 → Connection ID: 0x1234
  Switch to cellular: 10.0.0.50 → Connection ID: 0x1234 (same!)
  
Connection continues seamlessly

Senior

Q: Design a QUIC-based system that handles connection migration, handles network changes, and optimizes for mobile devices. How do you manage connection state and ensure reliability?

A:

class QUICSystem {
  private connections: Map<string, QUICConnection>;
  private connectionManager: ConnectionManager;
  private migrationHandler: MigrationHandler;
  
  constructor() {
    this.connections = new Map();
    this.connectionManager = new ConnectionManager();
    this.migrationHandler = new MigrationHandler();
  }
  
  // 1. Connection Management
  class QUICConnection {
    private connectionId: string;
    private streams: Map<number, Stream>;
    private ipAddress: string;
    private state: ConnectionState;
    
    constructor(connectionId: string) {
      this.connectionId = connectionId;
      this.streams = new Map();
      this.state = 'HANDSHAKE';
    }
    
    async handlePacket(packet: QUICPacket): Promise<void> {
      // Handle connection migration
      if (packet.sourceIP !== this.ipAddress) {
        await this.migrate(packet.sourceIP);
      }
      
      // Process packet
      if (packet.type === 'STREAM') {
        await this.handleStreamPacket(packet);
      } else if (packet.type === 'ACK') {
        await this.handleAck(packet);
      }
    }
    
    async migrate(newIP: string): Promise<void> {
      // Update IP address
      const oldIP = this.ipAddress;
      this.ipAddress = newIP;
      
      // Send migration acknowledgment
      await this.sendMigrationAck();
      
      // Update routing
      this.connectionManager.updateRouting(this.connectionId, newIP);
    }
  }
  
  // 2. Connection Migration
  class MigrationHandler {
    async handleMigration(connectionId: string, newIP: string): Promise<void> {
      const connection = this.connections.get(connectionId);
      
      if (!connection) {
        throw new Error('Connection not found');
      }
      
      // Validate migration
      if (!this.isValidMigration(connection, newIP)) {
        throw new Error('Invalid migration');
      }
      
      // Update connection
      await connection.migrate(newIP);
      
      // Update routing tables
      await this.updateRouting(connectionId, newIP);
    }
    
    isValidMigration(connection: QUICConnection, newIP: string): boolean {
      // Check migration token
      // Validate new IP
      // Check rate limits
      return true;
    }
  }
  
  // 3. Stream Management
  class StreamManager {
    createStream(connectionId: string): Stream {
      const connection = this.connections.get(connectionId);
      const streamId = this.generateStreamId();
      
      const stream = new Stream(streamId, connection);
      connection.streams.set(streamId, stream);
      
      return stream;
    }
    
    async sendData(streamId: number, data: Buffer): Promise<void> {
      const stream = this.getStream(streamId);
      
      // Send data (independent of other streams)
      await stream.send(data);
    }
  }
  
  // 4. Reliability (ACK Management)
  class ReliabilityManager {
    async handleAck(packet: QUICPacket): Promise<void> {
      const stream = this.getStream(packet.streamId);
      
      // Update ACK for stream
      stream.acknowledge(packet.ackNumber);
      
      // Retransmit if needed (per stream)
      if (stream.hasLostPackets()) {
        await stream.retransmit();
      }
    }
  }
  
  // 5. 0-RTT Optimization
  class ZeroRTTManager {
    async resumeConnection(connectionId: string): Promise<QUICConnection> {
      // Check if connection can be resumed
      const session = await this.getSession(connectionId);
      
      if (session && this.isValidSession(session)) {
        // 0-RTT: Resume immediately
        return await this.resume(session);
      } else {
        // 1-RTT: New handshake
        return await this.handshake();
      }
    }
  }
  
  // 6. Mobile Optimization
  class MobileOptimizer {
    async optimizeForMobile(): Promise<void> {
      // Adaptive congestion control
      this.useBBR(); // Better for mobile networks
      
      // Connection migration
      this.enableMigration();
      
      // Battery optimization
      this.reduceKeepAlive();
    }
  }
}

Features:

  1. Connection migration: Handle IP changes seamlessly
  2. Stream management: Independent streams, no blocking
  3. Reliability: Per-stream ACK and retransmission
  4. 0-RTT: Resume connections for faster handshake
  5. Mobile optimization: Adaptive congestion, battery optimization

Key Takeaways

  • QUIC: UDP-based transport protocol with built-in encryption
  • No head-of-line blocking: Streams are independent, lost packet only blocks one stream
  • Faster handshake: 0-RTT (resumed) or 1-RTT (new) vs 3.5-RTT for TCP+TLS
  • Connection migration: Connection survives IP changes using Connection ID
  • Built-in encryption: TLS 1.3 integrated, encryption by default
  • Multiplexing: Multiple streams over single connection
  • HTTP/3: Uses QUIC as transport
  • Benefits: Better for mobile, faster connections, no TCP blocking
  • Best practices: Handle connection migration, optimize for mobile, use 0-RTT when possible

About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.