Opening

You have written this code a hundred times. You define a Retrofit interface, you call getUser, you get back a response object, and you display it on screen. Maybe you even wrapped it in a nice repository pattern with some error handling. And honestly? It works great. But have you ever thought about what actually happens between the moment you call that function and the moment data appears? Because there is a whole world in between. We are talking DNS lookups, TCP handshakes, TLS negotiations, HTTP framing, server processing, and response parsing. And all of that happens every single time you make a network call. Today we are going to follow a single API call from your Kotlin code all the way to the server and back. Step by step. Millisecond by millisecond. And by the end, you will understand exactly why your "fast API" sometimes feels slow.

The Starting Point: Retrofit and OkHttp

Let us start with what you see in your code. You have a Retrofit interface with a suspend function — something like getUser that returns a User object. When you call it, Retrofit does not actually know how to make network calls. It delegates everything to OkHttp. Retrofit is essentially a code generator for OkHttp calls. It takes your annotations — the GET path, the query parameters, the headers — and builds an OkHttp Request object. Then it hands that request to OkHttp's dispatcher, which manages a pool of threads for executing network calls. If you are using suspend functions, Retrofit also bridges the callback-based OkHttp world into coroutine-land. But the actual networking? That is all OkHttp. And OkHttp is about to do a lot of work.

Step 1: DNS Lookup

The first thing that needs to happen is figuring out where to send this request. You specified a URL like api.example.com. But the internet does not route traffic by name. It routes by IP address — a numerical address like 93.184.216.34. So OkHttp needs to translate that domain name into an IP address. This is called a DNS lookup, and it works like a chain of phone books. First, your phone checks its local DNS cache — has it looked up this domain recently? If not, it asks the system resolver, which might ask your carrier's DNS server, which might ask a root nameserver, which directs it to the authoritative server for that domain. Eventually, an IP address comes back. This typically takes 20 to 50 milliseconds on a good mobile connection. But on a cold start with no cache? It can take 100 to 200 milliseconds. That is a fifth of a second just to figure out where to send your request. And you have not sent a single byte of data yet.

Step 2: The TCP Handshake

Now that OkHttp knows the server's IP address, it needs to establish a connection. The internet runs on TCP — Transmission Control Protocol — and TCP requires a handshake before any data can flow. It is called a three-way handshake, and it goes like this. Your phone sends a SYN packet to the server — "Hey, I would like to connect." The server receives it and sends back a SYN-ACK — "Sure, I acknowledge your request and here is my own." Your phone then sends a final ACK — "Great, we are connected." Three packets. Three trips across the network. This takes one and a half round trips, which on a mobile network with 50 millisecond latency means about 50 to 75 milliseconds. And here is the thing that might surprise you: this happens before any of your data is sent. It is pure overhead. The TCP handshake exists to make sure both sides agree on things like sequence numbers and window sizes — the bookkeeping that makes reliable data transfer possible. But it costs time. Real, measurable time.

Step 3: TLS Negotiation

But wait — you are using HTTPS, right? Of course you are. It is 2026 and Android enforces it by default. So after the TCP handshake completes, there is another handshake on top of it: the TLS handshake. This is where encryption gets set up. Your phone and the server need to agree on a cipher suite, exchange cryptographic keys, and verify the server's certificate. In TLS 1.3 — which is what modern Android uses — this takes one additional round trip. In older TLS 1.2, it could take two. So figure another 50 to 100 milliseconds. During this handshake, the server also sends its certificate, and your phone has to verify it — check the certificate chain, make sure it has not expired, make sure it has not been revoked. This is computationally expensive too, though modern hardware handles it quickly. So let us add it up so far. DNS: 50 milliseconds. TCP handshake: 50 milliseconds. TLS: 100 milliseconds. We are at 200 milliseconds and we have not sent a single byte of your actual HTTP request.

Step 4: The HTTP Request and Server Processing

Now, finally, OkHttp sends your HTTP request. This includes the method — GET in our case — the path, headers like authorization tokens and content types, and potentially a body. For a GET request, the body is empty, so the request itself is small — maybe a few hundred bytes. It flies across the wire quickly. Then the server receives it. It needs to parse the request, authenticate you, query a database or hit some internal services, build a response, serialize it to JSON, and send it back. Server processing time varies enormously. A well-optimized endpoint hitting a cache might respond in 10 milliseconds. A complex query joining multiple tables with no cache might take 500 milliseconds or more. For a typical API call, figure around 100 to 200 milliseconds for server processing. Then the response has to travel back to you. If it is a small JSON payload — say 5 to 20 kilobytes — the download is fast, maybe 50 to 100 milliseconds. Larger payloads take longer, obviously.

The Total Time Breakdown

So let us put it all together. For a fresh API call with no cached connections, here is what a typical timeline looks like. DNS lookup: 50 milliseconds. TCP handshake: 50 milliseconds. TLS negotiation: 100 milliseconds. Sending the request: 10 milliseconds. Server processing: 200 milliseconds. Downloading the response: 100 milliseconds. That is roughly 500 milliseconds. Half a second. And that is the optimistic case on a decent mobile connection. On a congested network or with a distant server, any of those numbers can double or triple. This is why when you test your API in Postman on your office Wi-Fi and it takes 100 milliseconds, but users on mobile report it feeling slow — the difference is all those handshakes and network conditions. The server time is the same. Everything around it changes.

Connection Pooling and Keep-Alive

Here is the good news. You do not pay that full 500 millisecond cost every time. OkHttp implements connection pooling. After the first request to a server completes, OkHttp keeps the TCP connection open. It stays in a pool, alive and ready. When you make a second request to the same server, OkHttp reuses that existing connection. No DNS lookup — the IP is cached. No TCP handshake — the connection is already established. No TLS negotiation — the encrypted session is already active. You skip straight to sending the HTTP request. That second call might take only 300 milliseconds instead of 500. The third one might be even faster if the server responds more quickly. By default, OkHttp keeps up to 5 idle connections in its pool and holds them open for 5 minutes. This is why your app feels faster after the first few API calls. It is not your imagination. The connections are literally warmer.

HTTP/2 and Multiplexing

Modern servers often support HTTP/2, and OkHttp will automatically negotiate it during the TLS handshake. HTTP/2 changes the game in a big way. With HTTP/1.1, each connection can only handle one request at a time. If you need to fetch a user profile, their posts, and their notifications, that is three sequential requests — or three separate connections. With HTTP/2, you can multiplex — send all three requests over a single connection at the same time. They are interleaved as frames on the wire, and OkHttp sorts them out on the receiving end. This means that instead of paying the connection setup cost three times, you pay it once. And instead of waiting for request one to finish before starting request two, they all fly in parallel. This is a massive win for apps that make multiple API calls at once. And OkHttp handles all of this transparently. You do not need to change your Retrofit interface. The multiplexing happens under the hood.

What Happens on the Kotlin Side

Let us come back to your coroutine for a moment. When you call that suspend function, Retrofit creates the OkHttp call and enqueues it on a background thread. Your coroutine suspends — it is not blocking any thread. While OkHttp does all the networking we just described — DNS, TCP, TLS, HTTP — your coroutine is just sitting there, suspended, costing almost nothing. When the response finally arrives, OkHttp invokes the callback, Retrofit resumes your coroutine on the dispatcher you specified — usually Dispatchers.IO — and then your converter factory — Moshi or Gson or whatever — deserializes the JSON into your Kotlin data class. Only then does your suspend function return with the result. The beautiful thing is that all of that complexity — the handshakes, the pooling, the multiplexing, the thread management — is hidden behind a single line of code. But it is happening. Every single time.

Why "Fast" APIs Feel Slow

So now you understand why there is a gap between server response time and perceived latency. Your backend team says "the endpoint responds in 50 milliseconds." And they are right — from the server's perspective. But your user on a mobile network experiences DNS plus TCP plus TLS plus server time plus download time. Add to that serialization time for parsing the JSON response, and any UI thread work needed to display the result. The user perceives maybe 600 to 800 milliseconds. Under poor network conditions, it could be over a second. This is not a bug. This is physics — the speed of light, the overhead of reliable encrypted communication, the realities of mobile radio networks. The fix is not making the server faster. It is reducing the number of round trips. Use connection pooling. Enable HTTP/2. Cache aggressively. Prefetch data. Batch requests. Every round trip you eliminate is 50 to 100 milliseconds saved.

Recap and What is Next

Let us wrap up what we have learned. A single Retrofit call triggers a cascade of network operations: DNS resolution to find the server, a TCP three-way handshake to establish a reliable connection, a TLS negotiation to encrypt the channel, the actual HTTP request and response, and finally deserialization back into your Kotlin objects. The minimum time for all of this is around 500 milliseconds on mobile, and connection pooling is the single biggest optimization that reduces subsequent calls. OkHttp handles most of this automatically, but understanding what it does helps you make better architectural decisions — like batching requests, prefetching data, and designing for offline scenarios. In the next video, we are going to dig deeper into TCP and HTTP themselves — how they guarantee delivery, why the first request is always the slowest, and what HTTP/2 and HTTP/3 actually change. See you there.