Under the Hood
Networking

DNS: how a name becomes an address

The lookup chain that turns api.hisaab.measdev.me into an IP — stub resolver to root to TLD to authoritative — plus caching, TTLs, the record types you actually use, why it rides UDP, and why "propagation" is a lie.

DNS: how a name becomes an address

In the last lesson we skipped one line: "turn api.hisaab.measdev.me into an IP address." That line is the Domain Name System, and it hides an entire distributed database that answers trillions of queries a day. You use it constantly — every fetch, every page load — and it fails in ways that will eat a whole afternoon if you don't understand its one central trick: caching, at every single hop. Almost every DNS mystery is a caching story.

Start from what you know: you type a name, you get to a server. Now go down.

The resolution chain

No single machine knows every name on the internet. Instead, the answer is assembled by walking a hierarchy, reading the name right to left. api.hisaab.measdev.me is really api.hisaab.measdev.me. — that trailing dot is the invisible root.

Four kinds of player are involved:

  • Stub resolver — the tiny client in your OS. It doesn't do the walking; it asks one resolver and trusts the answer. This is what getaddrinfo() (and therefore fetch) actually calls.
  • Recursive resolver — the workhorse (your ISP's, or 8.8.8.8 / 1.1.1.1). It does the legwork and caches aggressively.
  • Root and TLD servers — the top of the tree. Roots know where the .me servers are; the .me (TLD, top-level domain) servers know where measdev.me's authoritative servers are.
  • Authoritative nameserver — the machine that actually holds the records for measdev.me. Its answer is the truth; everyone else is quoting a cache.

Here's a cold lookup, top to bottom:

  your app            recursive resolver              the hierarchy
  (stub)                  (8.8.8.8)
    │                        │
    │  api.hisaab.measdev.me │
    ├───────────────────────►│
    │                        │   "who serves .me ?"
    │                        ├──────────────────────────►  Root servers
    │                        │◄──────────────────────────  "ask the .me TLD servers"
    │                        │
    │                        │   "who serves measdev.me ?"
    │                        ├──────────────────────────►  .me TLD servers
    │                        │◄──────────────────────────  "ask ns1.measdev.me (authoritative)"
    │                        │
    │                        │   "A record for api.hisaab.measdev.me ?"
    │                        ├──────────────────────────►  Authoritative NS
    │                        │◄──────────────────────────  "A = 203.0.113.10, TTL 300"
    │  203.0.113.10          │
    │◄───────────────────────┤   (now cached at the resolver)
    │                        │

Note who does the work: the stub asks once. The recursive resolver makes up to three more round trips walking root → TLD → authoritative. On a cold miss that's real time — tens of milliseconds, sometimes over a hundred if the servers are far. Which is exactly why none of it happens most of the time.

Caching and TTLs: the whole point

Every record carries a TTL (time to live) in seconds — the answer's own expiry date. TTL 300 means "you may cache me for 5 minutes; after that, ask again." That number, set by whoever runs the domain, is cached at every hop the answer passes through:

Cache layerWhere it livesTypical lifetime
Browser DNS cacheInside Chrome/Firefoxseconds to ~1 min
OS stub cacheYour machinerespects TTL
Recursive resolver cacheISP / 8.8.8.8respects TTL, shared by all its users
AuthoritativeThe sourceit is the source

The resolver cache is the load-bearing one. Because it's shared, the first user anywhere behind 8.8.8.8 who looks up your domain pays the cold-walk cost; everyone after them for the next TTL seconds gets a sub-millisecond cached hit. This is why a domain you've never visited resolves instantly anyway — someone warmed the cache for you.

The TTL is a tradeoff you set deliberately. Low TTL (30–60s) = changes take effect fast, but more query load and slightly slower average lookups. High TTL (86400 = a day) = fast and cheap, but a mistake or a planned IP change is pinned in caches worldwide for up to a day. The standard move is to lower the TTL a day or two before a planned migration, cut over, then raise it again.

Negative caching: the "no" is cached too

If you ask for a name that doesn't exist, the authoritative server returns NXDOMAIN ("no such domain") — and that answer is cached too, governed by the SOA record's minimum field. This is negative caching, and it's a classic trap: you fetch a subdomain before you've created its DNS record, get NXDOMAIN, then create the record — and it still fails for minutes, because your resolver cached the "no." You didn't do anything wrong; you're waiting out a cached negative.

The record types you'll actually touch

DNS holds many record types; in practice you deal with a handful. Using measdev.me as the running example:

TypeMaps a name toExample
AAn IPv4 addressapi.hisaab.measdev.me. A 203.0.113.10
AAAAAn IPv6 addressapi.hisaab.measdev.me. AAAA 2001:db8::10
CNAMEAnother name (an alias)www.measdev.me. CNAME measdev.me.
TXTArbitrary text (verification, SPF)measdev.me. TXT "v=spf1 ..."
MXMail server + prioritymeasdev.me. MX 10 mail.measdev.me.

Two gotchas worth internalizing. A CNAME is a redirect at the DNS layer — resolving www returns "actually, go resolve measdev.me," costing another lookup step; and a name with a CNAME can't also carry other records, which is why you can't CNAME a bare apex domain. TXT records are the internet's junk drawer: domain-ownership verification (Google, Apple), email anti-spoofing (SPF/DKIM), all live here as plain strings.

Why DNS rides UDP (mostly)

A DNS query is tiny — a question in, an answer out, both usually under 512 bytes. Running that over TCP would mean a three-way handshake (a full round trip) just to ask a one-packet question. So DNS uses UDP by default: fire the question in one packet, get the answer in one packet, no handshake, no connection state. For a lookup you do before every connection, that saved round trip matters enormously.

UDP has no delivery guarantee, so the resolver just retries on timeout — cheap, because the query is idempotent. DNS falls back to TCP in two cases: when the response is too large for a single UDP datagram (the server sets a "truncated" flag and the client retries over TCP), and for zone transfers between nameservers. So the rule is "UDP for the common case, TCP when the answer won't fit."

Hands-on: watch the walk with dig

dig +trace makes the resolver do its recursion out loud, printing each hop instead of just handing you the final answer. Annotated:

$ dig +trace api.hisaab.measdev.me

; <<>> DiG 9.18 <<>> +trace api.hisaab.measdev.me
;; global options: +cmd

.            518400  IN  NS  a.root-servers.net.   ← (1) the root nameservers
.            518400  IN  NS  b.root-servers.net.
;; Received 811 bytes from 127.0.0.53#53(127.0.0.53)

me.          172800  IN  NS  a0.nic.me.            ← (2) root replies: ".me lives here"
me.          172800  IN  NS  b0.nic.me.
;; Received 645 bytes from 198.41.0.4#53(a.root-servers.net)  ← asked a root

measdev.me.  86400   IN  NS  ns1.measdev.me.       ← (3) .me TLD replies: authoritative NS
measdev.me.  86400   IN  NS  ns2.measdev.me.
;; Received 122 bytes from 199.253.59.1#53(a0.nic.me)         ← asked a .me server

api.hisaab.measdev.me. 300 IN A 203.0.113.10       ← (4) authoritative: the actual answer
;; Received 74 bytes from 203.0.113.53#53(ns1.measdev.me)     ← asked the authoritative NS

Read it as a descent: root (1) points you to the .me servers (2), which point you to measdev.me's authoritative servers (3), which finally give you the A record (4) with its TTL 300. Each ;; Received ... from line names which server answered that step — you're literally watching the four-hop walk from the diagram above. The +trace bypasses your resolver's cache on purpose, so this is the full cold path every time.

Go deeper

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. Walk the cold resolution of api.hisaab.measdev.me naming each server the recursive resolver talks to, in order, and say which one gives the authoritative answer.
  2. You lower a record TTL from 3600 to 60, wait an hour, then change the IP. Why does that get you a fast cutover, and what would have happened if you skipped the wait?
  3. You created a subdomain record 30 seconds ago but fetch still fails with a DNS error. Given negative caching, what most likely happened and roughly how long until it clears?
  4. Why does DNS use UDP by default, and name the two situations where it switches to TCP.
  5. Someone says "my DNS change is still propagating." Correct them precisely: what is actually happening, and where does the delay live?
  6. You changed assetlinks.json (or any file a third party caches for 24–48h) and refreshing does nothing. What single question should you ask first, and why is it the same principle as a DNS TTL?