A customer opens Tiffin, taps on "Biryani House", and about a quarter of a second later the menu appears. To them it's one step. Underneath, a dozen things happened in order, on three or four different machines.
You don't need to know every detail of networking to design systems. But you do need a rough map of where the time goes. When a page is slow, the fix depends entirely on which step is slow, and people waste weeks optimising a database query when the real cost was three round trips across a mobile network.
So let's follow one tap all the way there and back.
The request we'll follow
The customer taps a restaurant. The app needs its menu, so it sends:
GET https://api.tiffin.app/restaurants/42/menu
Here is the whole journey, in order.
%% caption: One request, start to finish. Each step adds some time. sequenceDiagram autonumber participant A as App participant N as DNS participant S as Server participant D as Database A->>N: where is api.tiffin.app? N-->>A: an IP address A->>S: connect, TCP and TLS A->>S: GET /restaurants/42/menu Note over S: check and route to the handler S->>D: run the query D-->>S: rows Note over S: handler builds JSON S-->>A: response Note over A: app draws the menu
Step 1: Find the server (DNS)
The app knows a name, api.tiffin.app. The network only understands IP addresses, like 203.0.113.10. Turning the name into an address is the job of DNS, the Domain Name System.
The phone asks a DNS resolver (usually run by the mobile carrier or by a provider like Google or Cloudflare), which finds the answer and sends it back. The first lookup might take 20 to 100 milliseconds. After that the answer is cached on the phone for as long as the record's TTL allows, often a few minutes, so most requests skip this step completely.
For Tiffin version one, the DNS record points straight at our one server.
Step 2: Open a connection (TCP and TLS)
Now the phone opens a connection to that address. Two handshakes happen:
- TCP sets up a reliable connection. That costs one round trip: the phone says hello, the server answers, and the phone can start sending.
- TLS sets up encryption, so nobody on the café Wi-Fi can read the customer's address or card details. With modern TLS 1.3 that's about one more round trip.
A round trip is the time for a message to go from the phone to the server and back. On a good 4G connection to a server in the same country it's around 40 to 80 milliseconds. On a weak signal it can be several hundred.
So before a single byte of our request is sent, a fresh connection has already cost about two round trips: 100 to 150 milliseconds on a typical phone. This is why apps keep connections open and reuse them for the next request. The second tap on the same connection skips steps 1 and 2 entirely.
Step 3: Send the request (HTTP)
Over that connection, the app sends an HTTP request. It looks roughly like this:
GET /restaurants/42/menu HTTP/1.1
Host: api.tiffin.app
Authorization: Bearer eyJhbGciOi...
Accept: application/json
A method (GET), a path, and some headers: who you are, what format you want back. For a request that changes something, like placing an order, there's also a body, usually JSON.
Getting the request to the server takes about half a round trip.
Step 4: The server handles it
Our server receives the request. Inside, a few small steps happen, usually in well under a millisecond each:
- Parse the HTTP request.
- Authenticate. Check the token in the
Authorizationheader. Who is this? - Route. Match
GET /restaurants/:id/menuto the code that handles it. - Validate. Is
42a sensible restaurant id? - Run the handler. This is your business logic.
The handler is where the real work is. For a menu, it needs the restaurant's categories, dishes and prices, so it asks the database.
Step 5: The database answers
The server already has a few connections to the database open (opening one per request would be slow, and we'll look at connection pools in Module 5). It sends a query over one of them:
SELECT c.name AS category, m.id, m.name, m.price_paise, m.is_available
FROM menu_items m
JOIN menu_categories c ON c.id = m.category_id
WHERE m.restaurant_id = 42
ORDER BY c.position, m.position;
The database checks its index, reads perhaps 60 rows, and sends them back. If the data is in memory and the query uses an index, that's 1 to 5 milliseconds. If it's missing an index on a big table, it could be seconds. (The lesson on indexes is all about this.)
Step 6: The response travels back
The handler turns the rows into JSON:
{
"restaurant_id": 42,
"categories": [
{ "name": "Biryani", "items": [
{ "id": 7, "name": "Chicken dum biryani", "price_paise": 28000, "available": true }
] }
]
}
It sends back a response with a status code (200 OK), some headers, and that body. A typical menu might be 20 to 40 KB, compressed to maybe a quarter of that. Getting it back to the phone takes another half round trip, plus a little time to transfer the bytes.
Finally the app parses the JSON and draws the screen.
Where the time actually goes
Let's add it up for a customer on 4G with a 60 ms round trip, opening the app fresh.
| Step | Time | Notes |
|---|---|---|
| DNS lookup | ~40 ms | Zero if cached |
| TCP handshake | ~60 ms | One round trip. Zero on a reused connection |
| TLS handshake | ~60 ms | One round trip with TLS 1.3. Zero on a reused connection |
| Request to server | ~30 ms | Half a round trip |
| Server work | ~2 ms | Parse, auth, route, validate |
| Database query | ~3 ms | With a good index |
| Response to phone | ~40 ms | Half a round trip plus transfer |
| App draws the screen | ~20 ms | Parse JSON, lay out, render |
| Total | ~255 ms |
Look at the shape of that table. The server and the database together took about 5 milliseconds. The network took about 230.
On the second tap, with DNS cached and the connection reused, the total drops to around 95 milliseconds, and the network is still most of it.
This leads to one of the most useful instincts in system design:
For a user on a phone, the number of round trips usually matters more than how fast your server is.
That's why a screen that needs one request feels much faster than a screen that needs five requests one after another, even if each request is quick. When you design an API, try to give each screen what it needs in as few calls as possible.
These numbers are rough on purpose. A user in the same city on fibre might see a 10 ms round trip. A user on a crowded 3G network might see 400 ms. The point isn't the exact values, it's the proportions: network round trips are tens to hundreds of milliseconds, while a good database query is a few.
When the server side does matter
The network isn't always the problem. The server side becomes the bottleneck when:
- A query is missing an index. 3 ms becomes 2 seconds.
- The handler makes many database calls in a loop. Fetching 60 dishes with 60 separate queries turns 3 ms into 60 or more.
- The server is overloaded. At dinner time, requests queue up waiting for a free CPU, and that waiting time can dwarf everything else. Module 4 is about this.
- The handler calls another slow service. A payment provider might take 500 ms to answer.
You find out which by measuring: logging how long each step takes on the server, and comparing it with what users experience. Never guess.
Why is the first screen slow?
Tiffin's team notices that the very first screen after opening the app takes about 700 ms, but every screen after that takes about 150 ms. The server logs show every request took under 10 ms to handle. What's the likely cause, and what could you do about it?
Show one answer
The server is not the problem: it's under 10 ms every time. Add up where the extra ~550 ms on the first screen could come from, using the table above:
- Setup that later screens skip: the DNS lookup and the TCP and TLS handshakes for a fresh connection. That's about 40 + 60 + 60 = 160 ms.
- That leaves about 390 ms, roughly five extra requests at ~75 ms each (the request, server work, database query and response rows of the table; drawing the screen happens once). So the first screen is probably making about six requests one after another (say, user profile, then location, then the restaurant list, offers, cart and recommendations), each waiting for the last, while later screens need just one.
Things to try: combine the first screen's data into one request, start the connection early (for example, while the splash screen shows), and make sure the app reuses one connection instead of opening a new one per request.
The same path, for every client
The kitchen tablet asking "any new orders?" and the rider app saying "picked up" follow exactly the same path: DNS, connect, request, server, database, response. Only the handler changes.
That's why the next two lessons focus on the part you control most: what those requests look like. That's the API.
What to remember
- A request goes through DNS, connection setup (TCP and TLS), the HTTP request, the server, the database, and the response back.
- A new connection costs about two round trips before any data is sent. Reusing connections skips that.
- For mobile users, network round trips (tens to hundreds of milliseconds) usually dwarf server and database time (a few milliseconds).
- Fewer round trips per screen is one of the cheapest ways to make an app feel fast.
- The server side becomes the problem with missing indexes, queries in loops, overload or slow external calls. Measure each step instead of guessing.