TCP, UDP, HTTP, gRPC, WebSockets: When to Use Each
Learn the difference between TCP, UDP, HTTP, gRPC, and WebSockets. Practical guide on picking the right protocol for your backend system.
The main network protocols for backend development are TCP, UDP, HTTP (versions 1.1, 2, and 3), gRPC, WebSockets, and Server-Sent Events (SSE). Each solves a different problem: TCP gives you reliability, UDP gives you raw speed, HTTP gives you the standard request-response web, gRPC gives you high-performance typed service calls, and WebSockets give you persistent two-way connections. Choosing wrong causes real pain — polling every second when you should use WebSockets, or using WebSockets for a simple CRUD API.
This guide covers when to use each, the HTTP version differences that actually matter for performance, a protocol decision matrix, and how companies like Discord, Netflix, and Kubernetes picked their protocols.
The Foundation: TCP vs UDP
All internet communication runs on one of two transport protocols.
TCP (Transmission Control Protocol) is reliable. Every packet is guaranteed to arrive, in order, exactly once. If a packet is lost, TCP retransmits it automatically. The trade-off: slightly more overhead and latency — the three-way handshake (SYN → SYN-ACK → ACK) alone costs one round trip before data flows.
UDP (User Datagram Protocol) is fast but unreliable. Packets can arrive out of order, be duplicated, or get lost entirely. No retransmission. Your application handles errors.
TCP: "Did you get packet 47?" → "Yes" → "Good, sending packet 48"
UDP: Just sends packets. Hope for the best.Use TCP when: Data integrity matters — APIs, databases, file transfers, email. Use UDP when: Speed matters more than perfection — video streaming, gaming, voice calls, DNS.
Real example: Netflix video streaming can tolerate a few lost packets (you'll see a brief blip). Losing a credit card transaction cannot be tolerated. DNS lookups use UDP because speed matters and the client can just retry if needed.
HTTP: The Web Standard
HTTP is built on top of TCP. It's a request-response protocol — the client asks, the server answers.
# A simple HTTP request — you use this all the time
import requests
response = requests.get("https://api.example.com/users/123")
data = response.json()Use HTTP when: Building REST APIs, serving web pages, any standard client-server communication.
HTTP/1.1 vs HTTP/2 vs HTTP/3
The HTTP version you run has measurable latency and throughput consequences.
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Multiplexing | No (one req/connection) | Yes (streams) | Yes (streams) |
| Head-of-line blocking | Connection-level | Stream-level (TCP HOL remains) | Eliminated |
| Header compression | No | HPACK | QPACK |
| Connection setup | TCP handshake | TCP + TLS (1-2 RTTs) | 0-RTT (repeat connections) |
| TLS required | Optional | Optional but universal | Built-in |
| Browser support | Universal | Universal | ~95% (growing) |
| Server push | No | Yes | Yes |
| When to use | Legacy systems, simplicity | Most production services | Mobile-heavy, lossy networks |
Head-of-Line Blocking
HTTP/1.1 can only process one request per TCP connection at a time. Browsers work around this by opening 6 connections per domain — wasteful and limited.
HTTP/2 multiplexing allows multiple concurrent streams over a single connection. But TCP itself causes head-of-line blocking: if one packet is lost, all streams stall waiting for retransmission. This is a TCP problem, not an HTTP/2 problem.
HTTP/3 runs over QUIC (Quick UDP Internet Connections), Google's protocol that rebuilds TCP's reliability on top of UDP. QUIC gives each stream its own loss recovery — a lost packet only stalls that stream, not all concurrent ones. QUIC also implements TLS 1.3 natively, reducing connection setup to 1 RTT (or 0-RTT for resumed connections).
When to Use Each HTTP Version
- HTTP/1.1: Simple internal tools, webhooks, systems where simplicity beats performance
- HTTP/2: Default for production REST APIs and web services — all major load balancers (nginx, HAProxy, Caddy) support it out of the box
- HTTP/3: Mobile apps, streaming media, any service where your users are on unreliable networks; Cloudflare and Google already serve HTTP/3 by default
gRPC: High-Performance Service-to-Service
gRPC is built on HTTP/2 and uses Protocol Buffers (protobuf) for serialization instead of JSON.
Why does that matter? Protobuf is binary and much smaller than JSON. A JSON payload of 100 bytes might be 20 bytes as protobuf. At millions of requests per second, that adds up.
// Define your service in a .proto file
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}
message UserRequest {
string user_id = 1;
}
message UserResponse {
string name = 1;
string email = 2;
int64 created_at = 3;
}# Generated gRPC client call (Python)
import grpc
import user_pb2
import user_pb2_grpc
channel = grpc.insecure_channel('localhost:50051')
stub = user_pb2_grpc.UserServiceStub(channel)
response = stub.GetUser(user_pb2.UserRequest(user_id="123"))
print(response.name)gRPC supports four communication patterns:
- Unary: One request, one response (like HTTP)
- Server streaming: One request, stream of responses
- Client streaming: Stream of requests, one response
- Bidirectional streaming: Both sides stream simultaneously
Use gRPC when:
- Internal service-to-service communication (microservices)
- You need high throughput with low latency
- You're defining strict contracts between services
- Multiple programming languages in your stack (gRPC is polyglot — generate clients in Go, Python, Java, Rust from one
.proto)
Don't use gRPC when: Your client is a browser (gRPC-Web exists but requires a proxy) or you need human-readable request debugging.
WebSockets: Real-Time Two-Way Communication
HTTP is one direction at a time — client asks, server responds. WebSockets are different: once connected, both sides can send messages whenever they want.
HTTP: Client → Request → Server → Response → done
WebSocket: Client ↔ Server (persistent, bidirectional)A WebSocket connection starts as an HTTP request (Upgrade: websocket header), then switches protocols.
# FastAPI WebSocket example
from fastapi import WebSocket
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Echo: {data}")Use WebSockets when:
- Real-time features: chat, notifications, live feeds
- Collaborative tools (Google Docs-style editing)
- Live dashboards, trading platforms
- Multiplayer games
Don't use WebSockets when: You just need regular request-response APIs. WebSockets are stateful — sticky sessions or a shared pub/sub layer (Redis) is required when horizontally scaling.
Server-Sent Events (SSE): Server Push Made Simple
SSE is a one-way streaming protocol — the server pushes updates to the client over a standard HTTP connection. Simpler than WebSockets when you only need server-to-client updates.
# FastAPI SSE example
import asyncio
from fastapi.responses import StreamingResponse
async def event_stream():
for i in range(10):
yield f"data: Update {i}\n\n"
await asyncio.sleep(1)
@app.get("/stream")
async def stream():
return StreamingResponse(event_stream(), media_type="text/event-stream")SSE reconnects automatically if the connection drops. It works over standard HTTP, so CDNs, proxies, and load balancers handle it without configuration. Browser support is universal.
Use SSE when: The server pushes updates to clients (notifications, progress bars, live scores, AI token streaming) and clients don't need to send data back.
Protocol Overhead and Performance
Understanding the cost of each protocol helps you reason about performance at scale.
Connection Setup Cost
| Protocol | Setup Cost | Notes |
|---|---|---|
| UDP | ~0 | No handshake — fire and forget |
| HTTP/1.1 | 1 RTT (TCP) + 1-2 RTTs (TLS) | Each new connection pays full cost |
| HTTP/2 | 1 RTT (TCP) + 1-2 RTTs (TLS) | Amortized over many requests via keep-alive |
| HTTP/3 / QUIC | 1 RTT (new) / 0-RTT (resumed) | TLS built-in; resume is near-zero |
| WebSocket | 1 RTT (HTTP upgrade) | Then persistent — amortized over lifetime |
| gRPC | 1 RTT (TCP) + TLS | HTTP/2 multiplexing amortizes it heavily |
Serialization Cost
REST APIs typically use JSON, which requires string parsing. gRPC uses protobuf — a binary format that is 3-10x smaller and 5-10x faster to serialize/deserialize in benchmarks. For a microservice handling 50,000 RPS, switching from JSON over HTTP/1.1 to protobuf over gRPC can cut serialization CPU by 60-80%.
Connection Reuse
HTTP/1.1 requires multiple connections for parallel requests. HTTP/2 and gRPC reuse a single connection with multiplexed streams — fewer connections means less OS overhead, fewer TLS handshakes, and lower memory at high concurrency.
TLS/SSL: The Protocol Security Layer
TLS sits between your transport layer (TCP/UDP) and application protocol (HTTP, gRPC, WebSocket). It provides encryption, authentication, and integrity — the same TLS certificate and handshake mechanism works across all these protocols.
How TLS Works Per Protocol
- HTTP + TLS = HTTPS: Standard. Your load balancer (nginx, Caddy, AWS ALB) terminates TLS, then forwards plaintext HTTP/1.1 or HTTP/2 to backend services on your private network.
- gRPC + TLS: gRPC channels can be
insecure_channel(internal services on a VPN/service mesh) orsecure_channelwith TLS. Kubernetes clusters using mTLS (mutual TLS via Istio or Linkerd) authenticate both sides. - WebSocket + TLS = WSS:
wss://is WebSocket over TLS. Always use WSS in production — browsers block mixed-content WS connections on HTTPS pages. - HTTP/3 + QUIC: TLS 1.3 is mandatory and built into the QUIC protocol itself. You cannot run QUIC without TLS.
Certificate Pinning
Certificate pinning forces a client to reject TLS certificates not matching an expected fingerprint — useful for mobile apps calling your API. It prevents man-in-the-middle attacks even when a rogue CA is trusted by the OS. Downside: certificate rotations require app updates. Use it for high-security internal tooling or financial apps; skip it for general consumer apps where rotation complexity outweighs the benefit.
Choosing the Right Protocol: Decision Matrix
| Use Case | Best Protocol | Why |
|---|---|---|
| Public REST API | HTTP/2 (HTTPS) | Universal support, stateless, cacheable |
| Internal microservices | gRPC | Typed contracts, low overhead, streaming |
| Real-time chat / collaborative editing | WebSockets | Bidirectional, persistent connection |
| Push notifications / AI response streaming | SSE | Server-only push, simpler than WS, auto-reconnect |
| Video/audio streaming | HTTP/2 or HTTP/3 + DASH/HLS | QUIC handles packet loss on mobile |
| DNS resolution | UDP | Sub-millisecond, retries are cheap |
| File uploads | HTTP/2 | Reliable, built-in progress via content-length |
| Live game state sync | UDP or WebSockets | Low latency; UDP if you own the client |
| Event streaming (Kafka consumer) | gRPC streaming or WebSockets | High throughput, persistent |
| Health checks | HTTP/1.1 | Simple, no overhead needed |
REST/HTTP vs gRPC: The Core Trade-off
Choose REST/HTTP when:
- External-facing API (public, third-party integrations)
- Team unfamiliar with protobuf tooling
- Caching at CDN layer is needed (gRPC is not cacheable by standard CDNs)
- You want human-readable payloads for debugging
Choose gRPC when:
- Service-to-service on the same internal network
- Strict schema enforcement needed (proto is a contract)
- Low latency matters (payment processing, real-time pricing)
- Streaming RPCs needed (bi-directional sync, log tailing)
WebSockets vs SSE
| WebSockets | SSE | |
|---|---|---|
| Direction | Bidirectional | Server → client only |
| Protocol | Custom WS frames | Plain HTTP |
| Browser API | WebSocket | EventSource |
| Auto-reconnect | Manual | Built-in |
| CDN/proxy support | Needs WS-aware proxy | Works everywhere |
| Scaling | Sticky sessions or pub/sub | Stateless (each connection is independent) |
| Use case | Chat, gaming, collaborative editing | Notifications, live scores, AI streaming |
All Protocols at a Glance
| Protocol | Transport | Latency | Reliability | Browser Support | Bidirectional | Typical Use Case |
|---|---|---|---|---|---|---|
| TCP | — | Low | Guaranteed delivery | N/A (OS level) | Yes | Transport for all others |
| UDP | — | Lowest | Best-effort | N/A (OS level) | Yes | Gaming, DNS, video |
| HTTP/1.1 | TCP | Medium | Reliable | Universal | No | Simple APIs, legacy |
| HTTP/2 | TCP | Low | Reliable | Universal | No (multiplexed) | REST APIs, web apps |
| HTTP/3 | QUIC/UDP | Very low | Reliable | ~95% | No (multiplexed) | Mobile, media |
| gRPC | HTTP/2 | Very low | Reliable | Via gRPC-Web | Streaming | Microservices |
| WebSockets | TCP | Low | Reliable | Universal | Yes | Chat, gaming, collab |
| SSE | HTTP | Low | Reliable | Universal | No | Notifications, streaming |
Real-World Protocol Selection
How companies actually chose:
Discord uses WebSockets for real-time messaging. Every client maintains a persistent WebSocket connection to Discord's gateway. When you send a message, it travels over your WS connection — sub-100ms for most users. Discord also uses UDP for voice (via WebRTC), because dropping 20ms of audio is better than waiting for a TCP retransmit.
Netflix uses HTTP/2 and HTTP/3 for video delivery. QUIC's stream independence means a single lost packet on a spotty mobile connection doesn't freeze the entire video. Netflix's CDN (Open Connect) negotiates HTTP/3 with clients that support it, falling back to HTTP/2. Their API tier runs HTTP/2 internally between services.
Kubernetes API uses gRPC for internal cluster communication. kubectl talks to the API server over HTTPS (HTTP/2). Internal components — kubelet, controller-manager, scheduler — communicate via gRPC for tight protobuf contracts and streaming watch APIs (kubectl logs -f is a gRPC server-streaming RPC). The Kubernetes API is the most battle-tested large-scale gRPC deployment outside Google.
Stripe's public API is REST/HTTP/2. Webhooks are plain HTTP POST requests. This is deliberate: every language has an HTTP client; Stripe doesn't want to force protobuf tooling on developers. For their internal services, they use gRPC.
Figma uses WebSockets for multiplayer collaborative editing. Every cursor move, every node edit flows over a persistent WebSocket. Figma's servers maintain a conflict-resolution layer (CRDT-based) on top of the WS stream.
FAQ
What is the difference between TCP and UDP?
TCP (Transmission Control Protocol) guarantees delivery: packets arrive in order, with error checking and automatic retransmission of lost packets. This reliability costs a three-way handshake and extra overhead. UDP (User Datagram Protocol) sends packets without any guarantee — no ordering, no confirmation, no retransmission. UDP is faster because it skips all that. Use TCP whenever data loss is unacceptable (APIs, databases, file transfers). Use UDP when speed matters more than perfection and your application can handle loss (DNS, video streaming, online games, VoIP).
When should I use gRPC instead of REST?
Use gRPC for internal service-to-service calls where you control both the client and server. gRPC gives you: typed contracts enforced by protobuf schemas, 3-10x smaller payloads than JSON, native streaming (server/client/bidirectional), and HTTP/2 multiplexing. Use REST when building a public API — browser clients, third-party integrations, and CDN caching all work natively with HTTP. REST is also simpler to debug with curl and browser dev tools.
When should I use WebSockets vs Server-Sent Events?
Use WebSockets when the client also needs to send data: chat, collaborative editing, multiplayer games, trading platforms. Use SSE when only the server pushes data and the client just listens: live notifications, progress updates, AI token streaming (ChatGPT-style), live scores. SSE is simpler — it works over plain HTTP, reconnects automatically, and needs no special proxy configuration. WebSockets require WS-aware proxies and manual reconnection logic.
What is HTTP/2 multiplexing and why does it matter?
HTTP/1.1 processes one request per TCP connection at a time. Browsers work around this by opening up to 6 parallel connections per domain — wasteful and still limited. HTTP/2 multiplexing allows dozens of concurrent requests and responses over a single TCP connection using numbered streams. Each request/response is a stream; they interleave without blocking each other. This eliminates the need for multiple connections, reduces TLS handshake overhead, and cuts time-to-first-byte for pages that fetch many resources (scripts, images, API calls).
What protocol does Kubernetes use internally?
Kubernetes uses gRPC for internal cluster communication. The API server accepts HTTPS (HTTP/2) from external clients like kubectl, but internal components — kubelet, kube-proxy, controller-manager, scheduler — communicate with each other via gRPC over HTTP/2. Watch operations (kubectl get pods -w) are gRPC server-streaming RPCs. etcd, the Kubernetes backing store, also communicates via gRPC. This makes Kubernetes one of the largest real-world deployments of gRPC.
How does gRPC achieve high performance?
gRPC combines three performance advantages. First, protobuf serialization is binary and 3-10x smaller than equivalent JSON, reducing both network bytes and CPU parsing time. Second, HTTP/2 multiplexing lets a single connection carry many parallel RPCs without per-request connection setup overhead. Third, streaming allows gRPC to push results incrementally rather than waiting for a full response — a server-streaming RPC starts delivering the first result immediately. Together, these make gRPC 5-10x faster than JSON-over-HTTP/1.1 in high-throughput microservice scenarios.
What is the difference between HTTP long polling and WebSockets?
Long polling is an HTTP hack: the client sends a request, the server holds it open until there's new data (or a timeout), then responds. The client immediately sends another request. This simulates push over HTTP without requiring WebSockets. The downside: each "push" still requires a full HTTP request-response cycle including headers, and at high message rates the overhead compounds. WebSockets establish a persistent TCP connection and send framed messages bidirectionally with minimal overhead (2-14 bytes of frame header vs hundreds of bytes of HTTP headers). Use WebSockets for anything above a few messages per second or when latency under 100ms matters.
Related reading: OSI Model Explained · DNS Explained
Enjoyed this article?
Get weekly insights on backend architecture, system design, and Go programming.
Related Posts
Continue reading with these related posts
OSI Model Explained: 7 Layers of Networking Every Developer Should Know
The OSI model defines 7 layers of network communication. Learn what each layer does, how it maps to TCP/IP, debugging tools per layer, and why backend developers care about L3-L7.
DNS (Domain Name System) Explained: How It Works, Record Types, Security, and Cloud
Complete guide to how DNS works: resolution process, record types, TTL, DNSSEC, DNS over HTTPS, Route 53, Cloudflare, troubleshooting with dig, and common DNS mistakes engineers make.
GraphQL vs REST: Which API Style Should You Use?
GraphQL lets clients request exactly the data they need from a single endpoint, while REST exposes fixed resources over multiple URLs with standard HTTP verbs. Learn when each wins — with real N+1 examples, schema design, performance tradeoffs, tooling, and a decision matrix.