---
title: "DNS Resolution"
description: "Recursive vs. iterative lookup"
source: https://vizlearn.app/cs/networks/dns-resolution
---

# DNS Resolution

DNS resolution is the process that turns a hostname like `www.example.com` into an IP address. A recursive resolver walks the domain-name hierarchy from the root down, asking one server per level; in this lesson's trace the root and top-level-domain servers reply with referrals naming who to ask next, and only the authoritative server for the zone returns the address.

Picture a chain of receptionists, none of whom knows the answer. You ask the one in the lobby where to find a person; she has never heard of them, but she knows which building to try. That building's desk has never heard of them either, but knows which floor. Only the office manager on that floor actually has the name on a list. Three conversations, two of which produced no answer at all — and yet nobody wasted your time, because each one narrowed the search by exactly one level. DNS is that chain, with the extra detail that you keep notes.

## How DNS Resolution Works

A domain name is read right to left. `www.example.com` is a name inside the `example.com` zone, which was delegated by the `com` zone, which was delegated by the root. Each zone is run by whoever owns it, and the only thing the level above stores is a pointer — an `NS` record naming the servers for the zone below. That is the whole design: nobody holds the full namespace, so nobody has to be updated when a name changes.

Two different questions travel over the wire, and mixing them up is the single most common misreading of DNS. Your laptop's **stub resolver** asks a **recursive** question: *give me the answer or an error, and do not send me elsewhere.* It sends exactly one packet and waits. The **recursive resolver** — your ISP's, or a public one like `8.8.8.8` — then asks **iterative** questions on your behalf: *answer this if you can, otherwise tell me who can.* Iterative queries are the ones that walk the hierarchy. The stub never sees them.

In this model, a server that receives an iterative query says one of three things. It can return an **answer** (an `A` record — the address), which only the authoritative server for the zone ever does. It can return `NXDOMAIN` — the zone is mine and this name is not in it — which again only the authoritative server can honestly say. Or it can return a **referral**: an `NS` record naming the servers one level down. Here the root and TLD servers always refer; real servers can also reply NODATA or SERVFAIL, and a server authoritative for an upper zone answers from it directly. Watch the trace with that in mind and the shape of the protocol falls out: the resolver loops, each iteration replacing the server it asks while the question itself never changes.

The resolver caches everything it learns, referrals included, each with the TTL the record carried. That is what makes the referral design pay off: a cached `com NS` record shortens the walk for every `.com` name anyone ever asks for, not just the one that fetched it.

## DNS Resolution Step by Step

The default run is a **cold cache** resolving `www.example.com`, and it costs eight messages.

1. **Nothing is known.** The cache is empty, the four zone chips at the top of the canvas are all gray, and the hierarchy sits idle.
2. **Read the name backwards.** In `www.example.com`, the rightmost label is the top of the tree and `www` is the leaf. The chips on the canvas put that hierarchy in walking order — `(root) → com. → example.com. → www.example.com` — so the chips show the root and delegated zones followed by the host name inside example.com; www is not a separate delegated zone in this trace.
3. **The stub asks once.** One recursive query, `www.example.com A`, from your laptop to the resolver. From here on, everything happens without the stub's knowledge.
4. **Cache miss.** `cache.best_ns(name)` finds nothing, so the resolver falls back to the one address it knows by heart: a root server. An iterative resolver can bootstrap from configured root hints; forwarding resolvers may delegate the work to another recursive service.
5. **Query the root** for the full name `www.example.com`, not for "com".
6. **The root has never heard of it.** What it holds is a delegation: `com. NS a.gtld-servers.net`. About 1,500 top-level domains, not a billion addresses.
7. **The referral comes back.** This packet carries an `NS` record — the *name* of the next server to ask, and nothing about `www.example.com`. Three messages spent, zero progress toward the address — and yet one full level of the name is now resolved.
8. **Cache the referral** with a two-day TTL, before following it. For the next two days, no lookup under `.com` pays for this hop.

Beats 9 through 12 repeat that round trip exactly one level down: the resolver puts the *identical* question to `a.gtld-servers.net`, gets a second referral (`example.com. NS a.iana-servers.net`), and caches it too. Two replies so far, two `NS` records, and still nothing that answers the question.

13. **Query the authoritative server** for `example.com` — the bottom of the walk, run by whoever owns the zone.
14. **This one answers.** `www.example.com A 93.184.216.34`. It is the first and only `A` record in the entire trace, and it comes from the only server that could have produced it.
15. **The answer travels back** to the resolver, which exits its loop — a reply with an answer section is the only exit that returns a value.
16. **Cache the answer** with its TTL.
17. **The resolver hands the stub the address.** One question in, one answer out; the stub never learned there was a hierarchy at all.
18. **Eight messages.** And for the next hour, the same name costs two.

Run it yourself and the trace lines up: `dig +trace www.example.com` does the iterative walk from your own machine instead of handing it to a resolver, printing every reply as it lands. The referrals arrive as `NS` records in the AUTHORITY section, one block per level, and only the last response carries an `A` record in ANSWER.

## Why DNS Resolution Is Cheap the Second Time

Switch to **Warm TLD** and the run opens with `com NS a.gtld-servers.net` already in the table. `cache.best_ns(name) or ROOT` now returns the com nameserver, so the walk starts there. The root server is never contacted — it stays faded for the whole run, a visible hole in the hierarchy. Six messages instead of eight, and the code that produced the saving is the same code, unchanged.

**Warm answer** is the extreme version. The `A` record is still inside its TTL, so `resolve` returns on its second line and not one server in the hierarchy is contacted. Two messages: the stub's question and the reply. Repeat lookups of a name inside its TTL land here, which is what the cache is for.

Run the counterfactual and the design justifies itself. If root servers returned final addresses directly, every name on the internet would have to live in the root zone, and every change to any record anywhere would have to be published there. Delegation is what makes the namespace divisible; caching is what makes it survivable. A handful of root server names serve the entire internet precisely because each referral they hand out is reused by every subsequent lookup under that TLD, by every client sharing that resolver.

## Edge Cases

- **`NXDOMAIN`.** Select the **NXDOMAIN** scenario and watch `missing.example.com` walk the *full* hierarchy. The root and com referrals succeed — those zones are real, and neither server has any idea what names live below. Only `a.iana-servers.net` can look inside `example.com` and find nothing. A failed lookup costs exactly as much as a successful one.
- **Negative caching.** The denial is cached too, with a short TTL taken from the zone's `SOA` record. Without it, a typo in a retry loop would query the zone's authoritative server once per attempt; the cached delegations still skip the root and com.
- **A cached referral is not a cached answer.** The warm-TLD run still makes two query/reply round trips to the hierarchy. Caching shortens the walk by a prefix; only a cached answer removes it.
- **TTL expiry.** In this model, every saving above lasts as long as the TTL on the record; real caches may evict a row early, or serve an expired one when upstream fails (RFC 8767). When it expires the row is dropped and the next lookup pays for that hop again, which is why lowering TTLs before a migration is standard practice — and why it makes your resolver's traffic spike.
- **Multiple nameservers per zone.** Real zones publish two or more `NS` records and resolvers pick among them, retry on timeout, and fall back to TCP when a reply will not fit in a UDP packet.

## Common Mistakes

- **Thinking the root server "forwards" your query.** It does not forward anything. It answers your resolver with a referral and the conversation ends; the resolver opens a brand-new conversation with the next server. Every arrow on the canvas that leaves the resolver was initiated by the resolver.
- **Confusing recursive with iterative.** The stub asks one recursive question. The resolver asks several iterative ones. Both words describe the *query*, not the server — and a resolver that offers recursion to you either iterates on your behalf or forwards your question to one that does.
- **Assuming the question gets shorter as the walk descends.** This model asks for the full name at every level, including at the root. Real resolvers can use QNAME minimization to send only the names needed at each delegation step. Here it asks for the full name — it asks for `www.example.com` and lets each server answer with whatever it has.
- **Believing a referral contains the address.** It contains an `NS` record: the name of a server to ask, not the address you asked for. In the cold trace, two of the three replies from the hierarchy are referrals, and neither answers the question. (Real referrals often do carry addresses — but of the *nameservers* they point at, never of the name in the query. See the note on simplification below.)
- **Expecting `NXDOMAIN` to come back early.** For missing.example.com in this model, the authoritative example.com server supplies the denial. Other names can fail at an earlier delegation boundary, and a cached denial can answer without a new walk.
- **Treating the eight-message walk as the normal cost.** It is the cold-start cost. A repeat lookup inside the TTLs costs two, and a new name under a cached zone skips the hops above it. A name queried less often than its TTLs keeps paying the cold cost, and that is the cache working as designed.

## A Note on Simplification

The lesson runs one nameserver per zone; real zones publish several and resolvers race or rotate between them. Addresses and TTLs on the canvas are illustrative — `93.184.216.34` is the address `example.com` published for years, not a value you should rely on today.

The referral packets are stripped down. A real referral usually also carries **glue**: `A` and `AAAA` records for the nameservers it names, in the ADDITIONAL section, and a server that delegates to a nameserver living inside the delegated zone is required to send it (RFC 9471) — otherwise nothing could ever be resolved. That is why a referral on the wire does contain addresses; they are addresses of *servers to ask*, never of the name in the query, which is the distinction the lesson is drawing. The opposite case is skipped too: when a zone's nameservers live outside it, the parent may still send their addresses as sibling glue, or the resolver may have them cached; failing both, it runs a second, nested lookup just to find them.

Left out entirely: UDP retries and TCP fallback, EDNS(0), DNSSEC validation (which adds a parallel chain of signature checks to every step above), `CNAME` chasing, and the anycast routing that makes "one root server" mean hundreds of machines. RFC 1034 and RFC 1035 are the originals; [RFC 9499](https://www.rfc-editor.org/rfc/rfc9499.html) replaces RFC 8499 as the terminology reference.
