TCP vs UDP: Choosing the Right Protocol
TCP and UDP are transport protocols that move data between applications, but they make very different trade-offs. TCP provides ordered, reliable delivery through a connection-oriented model, while UDP sends independent datagrams with much less protocol overhead and no built-in guarantee that data will arrive.
For backend engineers, the choice is rarely about which protocol is faster in isolation. The important question is which delivery guarantees, latency characteristics, failure behavior, and application semantics the system actually requires.
Most application protocols use TCP because correctness is easier when the transport handles retransmission, ordering, and flow control. UDP becomes valuable when low latency, message independence, multicast-style behavior, or application-controlled reliability matters more than guaranteed ordered delivery.
Table of Contents
- Transport Protocol Responsibilities
- TCP Connection Model
- UDP Datagram Model
- TCP vs UDP Trade-Offs
- Choosing the Right Protocol
- Production Design Example
- Common Mistakes
- Production Checklist
- Conclusion
- More Articles to Read
Transport Protocol Responsibilities
IP delivers packets between hosts, but applications need more than addressing. They need a way to identify processes, manage communication state, and decide what should happen when packets are delayed, lost, duplicated, or delivered out of order.
TCP and UDP both use ports to deliver data to the correct application process, but their guarantees are very different.
What TCP Provides
TCP provides a connection-oriented byte stream. Applications write bytes into a connection, and TCP attempts to deliver those bytes reliably and in order to the receiving application.
TCP handles several important responsibilities:
- connection establishment
- ordered delivery
- retransmission of lost data
- duplicate suppression
- flow control
- congestion control
- connection termination
Applications such as HTTP/1.1, HTTP/2, PostgreSQL, Redis, SSH, SMTP, and many message-broker protocols rely on these properties.
What UDP Provides
UDP provides a connectionless datagram model. Each datagram is sent independently, and the protocol does not guarantee delivery, ordering, retransmission, or duplicate suppression.
UDP mainly provides:
- source and destination ports
- message boundaries
- basic integrity checking
- minimal transport-layer state
This simplicity makes UDP useful when applications prefer to control reliability themselves or when receiving stale data is worse than losing some data entirely.
TCP Connection Model
TCP maintains connection state at both endpoints. Before application data is exchanged, the endpoints establish a connection and agree that both sides are reachable.
Connection Establishment
A simplified TCP handshake looks like this:
Client Server
| -------- SYN --------------> |
| <----- SYN + ACK ----------- |
| -------- ACK --------------> |
| |
| ===== connection ready ==== |
This setup adds latency before useful application data can flow, although connection reuse allows many application requests to share one established connection.
Opening a new TCP connection for every backend operation is therefore usually less efficient than maintaining persistent connections or pools.
For example:
Without reuse:
Request
|
+-- TCP handshake
+-- TLS handshake
+-- HTTP request
+-- response
+-- connection close
With reuse:
TCP + TLS connection
|
+-- request 1
+-- request 2
+-- request 3
+-- request 4
This is one reason database clients, HTTP clients, and proxies commonly maintain connection pools.
Reliability, Ordering, and Flow Control
TCP assigns sequence information to transmitted data. When data is lost, the missing data can be retransmitted.
Sender:
1 2 3 4 5
Network:
1 2 X 4 5
|
v
packet 3 lost
TCP detects missing data
|
v
packet 3 retransmitted
|
v
Receiver sees:
1 2 3 4 5
This reliability simplifies application development because the application does not normally need to implement packet-level retransmission.
TCP also preserves byte ordering. If later data arrives before earlier data, the receiver may need to wait until missing earlier data is recovered before presenting the stream in order.
This can create head-of-line blocking: unrelated later data inside the same ordered stream can be delayed by loss affecting earlier data.
Flow control prevents a fast sender from overwhelming a slower receiver. Congestion control reduces transmission when the network appears overloaded.
These mechanisms make TCP robust for general-purpose reliable communication, but the guarantees are not free. They introduce state, buffering, retransmission, and coordination.
UDP Datagram Model
UDP does not establish a transport connection before sending data. The application creates a datagram and sends it toward a destination address and port.
Each datagram preserves its message boundary rather than becoming part of a continuous byte stream.
Independent Messages
Suppose an application sends five UDP messages:
Sender:
Message 1
Message 2
Message 3
Message 4
Message 5
Network:
1 2 X 4 5
Receiver may observe:
1 2 4 5
UDP itself does not retransmit message 3.
Messages can also arrive out of order:
Sent:
1 2 3 4
Received:
1 3 2 4
Whether that matters depends on the application.
For a live voice stream, losing one small chunk may create a brief audio artifact, while waiting to retransmit it could increase latency enough to make conversation feel worse.
For a financial transaction, losing one operation is unacceptable. TCP or a higher-level reliable protocol is usually the more natural fit.
Application-Controlled Reliability
UDP does not prevent an application from building reliability. It simply does not impose one specific reliability model.
An application can add:
- sequence numbers
- acknowledgments
- selective retransmission
- duplicate detection
- forward error correction
- custom congestion control
- expiration rules
This is useful when the application wants more control than TCP provides.
Modern protocols such as QUIC use UDP as a substrate while implementing connection state, encryption, congestion control, retransmission, and independent streams above it.
UDP therefore should not be interpreted as “unreliable applications.” It is better described as transport with fewer built-in guarantees.
TCP vs UDP Trade-Offs
The correct protocol depends on the application’s requirements rather than a universal performance ranking.
| Characteristic | TCP | UDP |
|---|---|---|
| Connection model | Connection-oriented | Connectionless datagrams |
| Reliable delivery | Built in | Not built in |
| Ordering | Guaranteed byte ordering | No ordering guarantee |
| Retransmission | Automatic | Application responsibility |
| Message boundaries | Byte stream | Preserved |
| Flow control | Built in | Application responsibility |
| Congestion control | Built in | Application/protocol responsibility |
| Protocol overhead | Higher | Lower |
Latency and Overhead
UDP has less transport-level setup because it does not require a TCP-style connection handshake.
That does not mean every UDP application is automatically faster.
If an application implements acknowledgments, retransmissions, security, connection state, and congestion control above UDP, much of the complexity returns at the application-protocol layer.
Similarly, TCP connection setup becomes less significant when applications reuse long-lived connections.
Protocol performance should therefore be evaluated using complete request behavior, including:
- connection establishment
- encryption setup
- connection reuse
- packet loss
- network round-trip time
- application message size
- number of concurrent streams
- retransmission behavior
Failure Behavior
TCP and UDP expose failures differently.
With TCP, a connection can fail because of:
- connection timeout
- connection refusal
- connection reset
- idle timeout
- failed retransmission
- remote endpoint termination
With UDP, sending a datagram successfully from the local process does not prove the remote application received it.
Application
|
| send()
v
Operating System
|
| datagram accepted locally
v
Network
|
X
datagram lost
Remote service receives nothing
The application therefore needs higher-level confirmation when delivery matters.
This distinction is critical: successful local transmission is not equivalent to successful remote processing.
Choosing the Right Protocol
The choice should start from application semantics.
The key questions are:
- Can messages be lost?
- Does ordering matter?
- Does stale data still have value?
- Is retransmission useful?
- Does every message require confirmation?
- Is latency more important than perfect delivery?
- Does the application already need custom transport behavior?
Backend and API Workloads
Most traditional backend workloads are naturally aligned with reliable transport.
Examples include:
- REST APIs
- database connections
- service-to-service RPC
- message broker connections
- file transfer
- administrative protocols
A database query cannot usually tolerate random chunks of its protocol disappearing. The same applies to a JSON API response or a message acknowledgment.
TCP is therefore a strong default when:
- all transmitted data matters
- ordering matters
- connection-oriented communication is acceptable
- application simplicity matters more than custom transport behavior
Advantages:
- reliable ordered delivery
- mature operating-system support
- built-in flow and congestion control
- simpler application logic
- strong ecosystem compatibility
Disadvantages:
- connection state
- handshake overhead for new connections
- ordered delivery can delay later data after packet loss
- less application control over transport semantics
Real-Time and Loss-Tolerant Workloads
UDP becomes more attractive when delivering old data later provides little value.
Examples may include:
- real-time voice
- real-time video
- online game state updates
- telemetry where newer samples replace older ones
- DNS queries
- custom low-latency protocols
Consider a game sending player positions every 50 ms:
t=0 ms position A
t=50 ms position B
t=100 ms position C
t=150 ms position D
If position B is lost, retransmitting it after position D has already arrived may provide little value. The newest state is more important than perfect historical delivery.
Advantages:
- minimal transport overhead
- no connection establishment requirement
- message boundaries are preserved
- applications can choose custom reliability semantics
- lost old data does not necessarily block newer data
Disadvantages:
- no guaranteed delivery
- no guaranteed ordering
- duplicate handling may be necessary
- flow and congestion behavior may require higher-level implementation
- application design becomes more complex when reliability is required
Production Design Example
Consider a logistics platform with public APIs, tracking events, internal services, and real-time vehicle-location updates.
Different communication paths have different requirements, so using one transport model everywhere would be unnecessary.
Architecture
Clients
|
| HTTPS
v
Public API
|
TCP-based traffic
|
+--------------+--------------+
| |
v v
PostgreSQL Message Broker
TCP TCP
|
v
Tracking Workers
Vehicle Devices
|
| frequent location updates
v
Telemetry Gateway
|
v
Location Processing
Public API requests require complete responses and predictable failure handling, so reliable transport is appropriate.
Database connections also require reliable ordered delivery because corruption or loss inside a database protocol cannot simply be ignored.
Vehicle telemetry has different semantics. Suppose every vehicle publishes location coordinates twice per second.
{
"vehicle_id": "TRUCK-42",
"sequence": 8192,
"latitude": 32.9984,
"longitude": -96.8891,
"speed": 54
}
If message 8192 is lost but message 8193 arrives 500 ms later, retransmitting the older location may not improve the current map position.
A UDP-style transport can make sense if the application is designed around latest-state semantics.
The receiving system can use sequence numbers:
latest_sequence = {}
def process_location(event):
vehicle_id = event["vehicle_id"]
sequence = event["sequence"]
previous = latest_sequence.get(vehicle_id, -1)
if sequence <= previous:
return
latest_sequence[vehicle_id] = sequence
update_vehicle_location(event)
Older or duplicated messages can be ignored.
Traffic and Failure Flow
Suppose a vehicle sends:
8191 -> delivered
8192 -> lost
8193 -> delivered
8194 -> delivered
The location service simply advances from state 8191 to 8193. The missing intermediate location does not prevent newer positions from being processed.
Now compare that with shipment creation:
POST /shipments
|
v
Shipment stored
|
X
response lost
This is a reliable transactional operation where uncertain outcomes need application-level idempotency and reconciliation. Transport reliability helps, but it cannot guarantee that the caller knows whether a remote business operation completed.
This illustrates an important distinction: TCP guarantees transport behavior, not business-level exactly-once processing.
Even over TCP, applications still need idempotency for operations where a connection can fail after the server performed the action but before the client received confirmation.
Production monitoring should therefore reflect the chosen transport.
For TCP workloads, useful metrics include:
- connection establishment failures
- connection resets
- active connections
- connection duration
- retransmission rate
- connection-pool utilization
- request timeouts
For UDP workloads, useful metrics include:
- datagrams sent and received
- sequence gaps
- duplicate messages
- out-of-order messages
- application-level loss rate
- processing delay
- socket buffer drops
Monitoring must match application semantics. A 1% packet-loss rate may be catastrophic for one protocol and almost invisible for another.
Common Mistakes
TCP and UDP are frequently reduced to “reliable versus fast,” but that oversimplification leads to poor protocol choices. Production behavior depends on connection reuse, network conditions, application semantics, and the reliability logic implemented above the transport.
| Mistake | Production Impact | Better Approach |
|---|---|---|
| Assuming UDP is always faster | Application-level reliability may recreate substantial overhead. | Measure complete protocol behavior. |
| Using TCP without connection reuse | Repeated handshakes increase latency and connection churn. | Use persistent connections and pools. |
| Assuming TCP prevents duplicate business operations | Retries after uncertain responses can duplicate actions. | Use application-level idempotency. |
| Using UDP when every message matters | Packet loss creates missing application data. | Use reliable transport or implement explicit reliability. |
| Retransmitting stale real-time data | Latency increases while old information loses value. | Prefer latest-state semantics where appropriate. |
| Ignoring message ordering with UDP | Older messages can overwrite newer state. | Add sequence numbers or timestamps. |
| Ignoring congestion behavior | High-volume senders can overload networks or receivers. | Use bounded sending and congestion-aware protocols. |
| Assuming successful send means successful processing | Lost packets or remote failures remain invisible. | Use application acknowledgments when completion matters. |
| Choosing protocols only from benchmark latency | Failure behavior and operational complexity are overlooked. | Evaluate semantics, recovery, and production constraints. |
| Using one protocol model for every workload | Different traffic patterns receive inappropriate guarantees. | Choose transport according to each communication path. |
Production Checklist
Transport selection should begin with application guarantees and failure semantics rather than protocol popularity.
- Define delivery requirements. Decide whether every message must arrive.
- Define ordering requirements. Determine whether newer data may be processed before older data.
- Identify stale-data behavior. Decide whether delayed messages still provide value.
- Prefer TCP for reliable request-response workloads. Use mature transport guarantees unless custom behavior provides real value.
- Reuse TCP connections. Avoid unnecessary handshakes and connection churn.
- Size connection pools globally. Consider all application replicas and downstream connection limits.
- Use explicit timeouts. Bound connection establishment and request waiting.
- Design application idempotency. Transport reliability cannot guarantee exactly-once business execution.
- Add sequencing for UDP state updates. Reject stale or duplicate messages where ordering matters.
- Measure packet loss. Understand how much loss the application can tolerate.
- Control UDP send rates. Do not assume the receiver or network can absorb unlimited traffic.
- Monitor retransmission behavior. High TCP retransmissions can indicate network congestion or loss.
- Test degraded networks. Simulate latency, packet loss, reordering, and connection resets.
- Test connection interruption. Verify application behavior when a connection breaks mid-operation.
- Separate transport from business guarantees. Treat successful transport delivery and successful business processing as different concepts.
- Choose based on workload semantics. Do not select UDP only for perceived speed or TCP only from habit.
Conclusion
TCP and UDP represent two different transport philosophies. TCP provides a reliable ordered byte stream and handles retransmission, flow control, and congestion control. UDP provides independent datagrams with minimal transport-level behavior and leaves more decisions to the application.
Most backend APIs, databases, and service-to-service communication benefit from TCP's guarantees. UDP becomes more attractive when freshness matters more than complete delivery, latency must remain low during loss, or the application needs transport behavior that differs from TCP's ordered stream model.
Key Takeaway: choose the transport protocol from application semantics. Use TCP when reliable ordered communication simplifies correctness; use UDP when independent messages, loss tolerance, or application-controlled reliability provide a meaningful architectural advantage.
Comments (0)