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 thereforefetch) 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
.meservers are; the.me(TLD, top-level domain) servers know wheremeasdev.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 layer | Where it lives | Typical lifetime |
|---|---|---|
| Browser DNS cache | Inside Chrome/Firefox | seconds to ~1 min |
| OS stub cache | Your machine | respects TTL |
| Recursive resolver cache | ISP / 8.8.8.8 | respects TTL, shared by all its users |
| Authoritative | The source | it 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.
Drive it yourself. Resolve a name and watch the resolver walk root → TLD → authoritative; the answer lands in the caches with a live TTL countdown. Resolve again before it expires and you get an instant hit — no walk. Let the TTL run to zero and the next lookup is cold again. Flip who's asking to another user on the same resolver: their device cache is empty, yet they still get an instant hit off the shared resolver cache someone else warmed. Try the www (CNAME, extra hop) and ghost (NXDOMAIN, the "no" is cached too) names as well.
empty — next lookup is cold
empty — next lookup is cold
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:
| Type | Maps a name to | Example |
|---|---|---|
A | An IPv4 address | api.hisaab.measdev.me. A 203.0.113.10 |
AAAA | An IPv6 address | api.hisaab.measdev.me. AAAA 2001:db8::10 |
CNAME | Another name (an alias) | www.measdev.me. CNAME measdev.me. |
TXT | Arbitrary text (verification, SPF) | measdev.me. TXT "v=spf1 ..." |
MX | Mail server + priority | measdev.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 NSRead 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
- Cloudflare Learning Center — "What is DNS?" — The clearest reference diagrams of the resolver → root → TLD → authoritative chain; skim it if the ASCII walk above went by too fast.
- Julia Evans — "Implement DNS in a Weekend" — You do not truly get the wire format until you parse it; this builds a resolver from scratch and the packet structure finally clicks.
- RFC 1034 §2–3 — "Domain Names: Concepts and Facilities" — The original design, and unusually readable for an RFC; the hierarchy and caching model here is exactly what still runs today.
Check yourself
Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.
- 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.
- 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?
- 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?
- Why does DNS use UDP by default, and name the two situations where it switches to TCP.
- Someone says "my DNS change is still propagating." Correct them precisely: what is actually happening, and where does the delay live?
- 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?