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.
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?