IPv4 Addressing and Subnetting
Imagine a company with 500 employees spread across four floors. A single router connects the building to the internet. Every laptop, printer, and phone needs an address. But the router should not have to remember 500 individual routes. It needs one compact rule that captures "everything in this building." This is the exact problem IPv4 addressing and subnetting solve. Together they give every device a unique 32-bit identity. They also let routers reason about entire groups of devices as a single, compressible unit. The internet works only because addresses are hierarchical, not flat. Consider the roughly 4 billion IPv4 addresses that exist. If each required its own individual routing table entry, no router could cope. No router on Earth could hold the IP forwarding tables needed to reach everyone. Subnetting turns a flat 32-bit number into a two-level structure. With further division, it becomes multi-level. There is a network portion that routers care about. There is also a host portion that only matters locally. This is the same idea that lets a postal system route mail. It uses "country, city, street" instead of memorizing every resident's exact coordinates. This article builds the addressing model from first principles. It covers the 32-bit address, the classful scheme that came first, and its failure. It also covers the subnet mask that generalized it. We derive, step by step, how an administrator carves an address block into smaller subnets. We show how many usable hosts each subnet holds. We also show how a router decides, address in hand, which outgoing interface to use. By the end you will be able to take an address block like `192.168.4.0/22`. You will split it into a specified number of subnets. You will compute every subnet's network address, broadcast address, and usable host range. You will also explain precisely why the arithmetic works. This groundwork is what makes CIDR and Hierarchical Address Aggregation possible later. It also makes NAT possible later.
- An IPv4 address is a 32-bit number split conceptually into a network part and a host part.
- A subnet mask (or `/n` prefix length) tells you exactly where that split happens.
- Subnetting borrows bits from the host part to create multiple smaller networks from one larger block.
- Given `n` host bits, a subnet holds `2^n` addresses total but only `2^n − 2` usable host addresses (network and broadcast reserved).
- Routers use longest prefix match against the network portion to decide how to forward a packet.
- Subnetting is the direct ancestor of CIDR and Hierarchical Address Aggregation, which removes rigid class boundaries and lets prefixes span any bit boundary.
Subnetting works because ANDing an IP address with its mask deterministically separates a fixed "network" prefix from a variable "host" suffix, letting routers group millions of addresses behind one compact routing decision.
The idea
An IPv4 address is a 32-bit unsigned integer, conventionally written in dotted-decimal notation as four 8-bit octets separated by dots, e.g. `192.168.1.10`. Structurally, every address is partitioned into two contiguous bit-fields: a network prefix, which identifies the physical or logical network (subnet) a host belongs to, and a host identifier, which identifies that specific host within the subnet. This partition is what makes IP addressing hierarchical rather than flat: two hosts share a network prefix if and only if they are reachable via the same local link without an intervening router. The boundary between network and host bits is not fixed by the address itself — it must be communicated alongside the address. Historically this used classful addressing (Class A: first bit `0`, 8-bit network / 24-bit host; Class B: first two bits `10`, 16/16 split; Class C: first three bits `110`, 24/8 split), where the class was inferred from the leading bits of the address. Classful addressing wasted enormous numbers of addresses — an organization needing 300 hosts had to take an entire Class B block of 65,534 addresses because a Class C block (254 hosts) was too small. This drove the introduction of the subnet mask: an explicit 32-bit bit-mask, with `1`s marking network-prefix bits and `0`s marking host bits, that a network administrator sets independently of the address's class. Equivalently, the mask length is written in CIDR slash notation as `/n`, meaning the first `n` bits (from the most significant bit) are network bits. `255.255.255.0` and `/24` denote the identical mask.
Subnetting is the act of subdividing one address block into several smaller blocks by moving the network/host boundary further into the host portion — i.e., increasing `n`. If an organization is allocated `10.0.0.0/16` (a Class-B-sized block), it can carve it into four equal subnets by extending the mask to `/18`, borrowing 2 host bits to become subnet-selector bits. Each resulting subnet is a self-contained broadcast domain: all hosts in it share the same network address (computed by bitwise-ANDing any host address with the mask) and the same broadcast address (obtained by setting all host bits to `1`). Two reserved addresses per subnet — the all-zeros host address (the network address itself) and the all-ones host address (the local broadcast address) — are not assignable to individual hosts, which is why a subnet with `h` host bits provides `2^h − 2` usable addresses rather than `2^h`.
How it works
Subnetting proceeds in two symmetric directions: designing subnets from a block (given to an administrator provisioning a network) and classifying an address into its subnet (performed by every router and host on every packet). Both operations reduce to the same bitwise arithmetic.
Step 1 — Represent the address and mask in binary. Convert each dotted-decimal octet to 8 bits; the mask has exactly `n` leading `1`s followed by `32 − n` trailing `0`s.
Step 2 — Compute the network address. `network = address AND mask` (bitwise, per bit, across all 32 bits). This zeroes out the host bits, producing the base address of the subnet.
Step 3 — Compute the broadcast address. `broadcast = network OR (NOT mask)`. This sets every host bit to `1`.
Step 4 — Enumerate usable host addresses. Everything strictly between `network` and `broadcast` (exclusive) is assignable to an interface: `2^(32−n) − 2` addresses.
Step 5 — Subdividing a block. To split a `/n` block into `2^k` equal subnets, extend the mask to `/(n+k)`, i.e. borrow `k` bits from the host field to serve as a subnet ID. Each of the `2^k` possible values of those `k` bits designates one subnet; the resulting subnets partition the original block exactly, with no overlap and no gaps, because binary values of `k` bits enumerate `0` through `2^k − 1` exhaustively and disjointly.
Step 6 — Routing (longest prefix match). A router holds a table of `(prefix, prefix-length, next-hop)` entries. Given a destination address, it computes `address AND mask` for every entry whose length is `n_i`, and among all entries that match, forwards using the one with the largest `n_i` (the most specific match) — this is what makes hierarchical subnetting and later CIDR aggregation coexist correctly in the same table. ```pseudocode
# --- Designing subnets: split a CIDR block into N equal subnets --- function SPLIT_BLOCK(base_address, base_prefix_len, num_subnets): k = ceil(log2(num_subnets)) # extra bits needed new_prefix_len = base_prefix_len + k assert new_prefix_len <= 32, "block too small for that many subnets" host_bits = 32 - new_prefix_len subnet_size = 2 ^ host_bits # addresses per subnet base_int = IP_TO_INT(base_address) subnets = [] for i in 0 .. (2^k - 1): net_int = base_int + i * subnet_size network = INT_TO_IP(net_int) broadcast = INT_TO_IP(net_int + subnet_size - 1) first_host = INT_TO_IP(net_int + 1) last_host = INT_TO_IP(net_int + subnet_size - 2) subnets.append({ network: network, prefix_len: new_prefix_len, broadcast: broadcast, usable_range: (first_host, last_host), usable_count: subnet_size - 2 }) return subnets
# --- Classifying: which subnet does an address belong to? --- function ADDRESS_TO_SUBNET(address, prefix_len): mask = MASK_FROM_PREFIX(prefix_len) # n ones then (32-n) zeros network = IP_TO_INT(address) AND mask broadcast = network OR (NOT mask AND 0xFFFFFFFF) return { network: INT_TO_IP(network), broadcast: INT_TO_IP(broadcast), prefix_len: prefix_len }
# --- Router forwarding: longest prefix match --- function LOOKUP_ROUTE(dest_address, routing_table): best = null best_len = -1 for entry in routing_table: # (prefix, prefix_len, next_hop) mask = MASK_FROM_PREFIX(entry.prefix_len) if (IP_TO_INT(dest_address) AND mask) == IP_TO_INT(entry.prefix): if entry.prefix_len > best_len: # prefer more specific match best = entry best_len = entry.prefix_len if best == null: return DEFAULT_ROUTE return best.next_hop ```
Complexity & correctness
Space/time to compute network and broadcast addresses. Given a 32-bit address and mask, computing `network = address AND mask` and `broadcast = network OR (NOT mask)` is `O(1)` — a fixed, small number of machine-word bitwise operations independent of any input "size" (addresses are always 32 bits). This constant-time property is exactly why hardware and routers can perform it at line rate on every packet.
Splitting a block into subnets. `SPLIT_BLOCK` runs in `O(2^k)` time and space, where `k` is the number of borrowed bits, because it must materialize one record per subnet. This is optimal: any correct algorithm must produce `2^k` distinct output records, so `Ω(2^k)` work is unavoidable if all subnets are enumerated explicitly.
Routing table lookup. The naive `LOOKUP_ROUTE` scans all `R` entries, giving `O(R)` per packet. Real routers instead use a trie (Patricia trie / multi-bit trie) keyed on prefix bits, achieving `O(W)` lookup where `W = 32` is the address width — independent of table size — because the trie depth is bounded by the bit-length of the address, not the number of entries. This is the practical mechanism behind longest-prefix-match hardware and is a core enabler for CIDR and Hierarchical Address Aggregation, which depends on fast lookup even as tables grow to hundreds of thousands of prefixes.
Correctness of the subnet partition (proof sketch). Claim: for a block `B` with prefix length `n` and `k` borrowed bits, the `2^k` subnets produced by `SPLIT_BLOCK` are pairwise disjoint and their union is exactly `B`.
- Disjointness: Each subnet `i` occupies the integer range `[base + i·S, base + i·S + S − 1]` where `S = 2^(32−n−k)` is the subnet size. For `i ≠ j`, these ranges are disjoint because consecutive ranges are contiguous and non-overlapping: the largest address in range `i` is `base + i·S + S − 1 = base + (i+1)·S − 1`, strictly less than the smallest address `base + (i+1)·S` in range `i+1`.
- Coverage: The union of ranges for `i = 0` to `2^k − 1` spans `[base, base + 2^k·S − 1] = [base, base + 2^(32−n) − 1]`, which is precisely the address range of the original block `B` (size `2^(32−n)`). Hence every address in `B` lies in exactly one subnet.
- Reserved addresses: Within each subnet, the network address (all host bits 0) is the unique smallest value, and the broadcast address (all host bits 1) is the unique largest value, both fixed functions of `i`; excluding these two leaves exactly `2^h − 2` usable addresses per subnet where `h = 32 − n − k`.
This establishes that subnetting is a lossless, exhaustive partition — no address is duplicated or lost, which is the property that lets independently-configured subnets be safely merged back into a single routing entry (aggregation), foreshadowing CIDR and Hierarchical Address Aggregation.
Tradeoffs. Borrowing more bits (finer subnets) increases administrative flexibility and limits broadcast-domain size (fewer collisions/floods per LAN) but wastes addresses: every subnet loses 2 addresses to network/broadcast regardless of how few hosts it holds, and rigid equal-size splitting can leave small subnets starved while large ones sit half-empty — a problem later addressed by variable-length subnet masking (VLSM), which allows different subnets from the same block to use different prefix lengths tailored to actual host counts.
Worked example
Consider an ISP allocating `172.16.0.0/20` to a campus that needs four buildings of roughly 100–500 hosts each, plus a small point-to-point link. We use VLSM (variable-length subnetting) rather than equal splitting, since building sizes differ. Block: `172.16.0.0/20` → `172.16.0.0` to `172.16.15.255`, 4096 total addresses.
| Requirement | Hosts needed | Chosen prefix | Host bits | Usable hosts | Subnet range |
|---|---|---|---|---|---|
| Building A | 500 | /23 | 9 | 510 | 172.16.0.0 – 172.16.1.255 |
| Building B | 250 | /24 | 8 | 254 | 172.16.2.0 – 172.16.2.255 |
| Building C | 120 | /25 | 7 | 126 | 172.16.3.0 – 172.16.3.127 |
| Building D | 60 | /26 | 6 | 62 | 172.16.3.128 – 172.16.3.191 |
| Point-to-point link | 2 | /30 | 2 | 2 | 172.16.3.192 – 172.16.3.195 |
Step-by-step for Building A (`172.16.0.0/23`): 1. Mask for `/23`: `11111111.11111111.11111110.00000000` = `255.255.254.0`. 2. Network address = `172.16.0.0 AND 255.255.254.0` = `172.16.0.0` (already aligned since it's the first subnet). 3. Broadcast address = network with host bits set to 1: third octet's last bit and all of the fourth octet → `172.16.1.255`. 4. Usable range: `172.16.0.1` through `172.16.1.254` → `2^9 − 2 = 510` usable addresses. ✓ Covers the 500-host requirement with headroom.
Step-by-step for Building C (`172.16.3.0/25`), verifying alignment after prior allocations: 1. After A (`172.16.0.0/23`) and B (`172.16.2.0/24`), the next free address is `172.16.3.0`. 2. Mask for `/25`: `255.255.255.128`. 3. Network = `172.16.3.0 AND 255.255.255.128` = `172.16.3.0`. Check: `3 AND ...` in the fourth octet, `0 AND 10000000 = 0`. Correct — `172.16.3.0` is already a valid `/25` boundary (0 is a multiple of 128). 4. Broadcast = `172.16.3.0 OR 0.0.0.127` = `172.16.3.127`. 5. Usable hosts: `172.16.3.1` to `172.16.3.126` = `126` addresses, satisfying the 120-host need.
Verifying no overlap (the correctness property proved above): Building D starts at `172.16.3.128`, immediately after Building C's broadcast address `172.16.3.127` — contiguous, no gap, no overlap, exactly as guaranteed by the disjointness argument. The point-to-point link then takes the next four addresses `172.16.3.192`–`.195` (a `/30`, giving exactly 2 usable addresses for the two router interfaces), leaving `172.16.3.196`–`172.16.15.255` free for future growth.
Router's-eye view: if a packet arrives destined for `172.16.3.140`, a router with routes to both `172.16.3.128/26` (Building D) and the aggregate `172.16.0.0/20` (default route toward the campus) performs longest-prefix match: `172.16.3.140 AND 255.255.255.192 = 172.16.3.128`, matching the `/26` entry exactly, which is more specific (`26 > 20`) and therefore wins — the packet is forwarded directly into Building D's LAN rather than following the coarser aggregate route.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Forgetting to subtract network + broadcast | Computing `2^h` instead of `2^h − 2` usable hosts | Always reserve the all-zero and all-one host values per subnet |
| Off-by-one in subnet boundaries | Assuming broadcast of one subnet and network of the next differ by 2, not 1 | Broadcast address is `network + size − 1`; the next subnet starts immediately after, at `+ size` |
| Confusing prefix length with subnet count | Believing `/24` always means "256 hosts" regardless of context | Host count is `2^(32−n) − 2`; always recompute from the actual prefix length |
| Misapplying classful intuition | Treating `/25` in a Class C block as invalid because "that's not a real class" | Classful boundaries are historical; any `/n` from 0–32 is a legal CIDR-style mask |
| Overlapping VLSM allocations | Manually assigning subnets without tracking the running address offset | Always allocate contiguously in decreasing size order and verify with the disjointness check |
| Router misconfigured with wrong mask | A host mask that doesn't match its neighbors' means it computes the wrong network address for the same physical LAN | All hosts on one broadcast domain must share the identical mask/prefix length |
Pioneers & history
The addressing scheme described here emerged from the foundational work on internetworking protocols in the 1970s and 1980s, when engineers realized a flat, unstructured address space could never scale to a global network of networks.
Origins · 1 minJon Postel and how this was discovered
Jon Postel served as the RFC editor and de facto authority on internet number allocation for nearly three decades, co-authoring the specifications that defined IPv4 addressing, classful networks, and the datagram format itself. His editorial stewardship — meticulous, low-drama, deeply technical — shaped the internet's addressing conventions long before any formal governance body existed, and his early recognition that address space would eventually be scarce motivated the later shift toward subnetting and classless allocation.
Where this matters
Every enterprise network, cloud VPC, and home router configuration relies on subnetting to divide address space into manageable, isolated broadcast domains — a `/24` for the accounting VLAN, a `/28` for a guest Wi-Fi segment, a `/30` for a router-to-router link. Cloud platforms (AWS VPCs, Azure virtual networks, GCP subnets) expose subnetting directly to users when they carve a `10.0.0.0/16` VPC into per-availability-zone or per-tier subnets. DHCP servers hand out addresses scoped to a subnet's usable range, and firewalls write rules in terms of subnet boundaries rather than individual hosts. Internet Service Providers use the same arithmetic at far larger scale when allocating customer blocks, and backbone routers rely on the longest-prefix-match principle described here — generalized without classful assumptions — to forward packets among hundreds of thousands of routes, a scaling story completed by CIDR and Hierarchical Address Aggregation. Address scarcity created by this same 32-bit ceiling is also what makes NAT essential in nearly every home and enterprise gateway, letting one public address serve an entire private subnet.
Ask the tutor: Can you walk me through subnetting `10.20.0.0/22` into subnets sized for 100, 50, 20, and 4 hosts using VLSM, and show that they don't overlap?
Why do we subtract 2 from the usable host count instead of using every address in a subnet?
Every subnet reserves its two extreme addresses for special roles that are structural, not optional. The address with all host bits set to zero is defined to be the network address — it identifies the subnet itself in routing tables and cannot be assigned to a device, because doing so would make "the subnet" and "a host in the subnet" indistinguishable. The address with all host bits set to one is the local broadcast address — sending a packet there delivers it to every host on the segment simultaneously, which is essential for protocols like ARP and DHCP discovery. If either were assignable to a single host, that host would either be unreachable (confused with the network identifier) or would receive every broadcast meant for the whole segment as if it were addressed personally, breaking the semantics both routers and hosts rely on.
What's the practical difference between a subnet mask like 255.255.255.0 and writing /24?
None — they are two notations for the identical 32-bit value, and any tool that accepts one should let you derive the other. `255.255.255.0` in binary is 24 leading ones followed by 8 zeros, which is exactly what `/24` denotes. The dotted-decimal mask form was standard under classful addressing conventions and remains common in consumer router UIs and some configuration files. The slash notation (CIDR notation) is more compact, unambiguous about the exact bit boundary, and is the form used almost universally in modern routing tables, BGP announcements, and cloud infrastructure configuration, which is why it's worth being fluent converting between the two.
Can subnet masks have non-contiguous bits, like 11111111.00000000.11111111.00000000?
Technically the original specification allowed arbitrary bit patterns, but in practice this is never used and virtually every implementation assumes contiguous masks. A non-contiguous mask breaks the clean arithmetic that makes subnetting efficient: the network address is no longer simply "everything before some boundary," and computing broadcast, usable ranges, and route aggregation all become far messier without any compensating benefit. Modern standards (and essentially all routers) require contiguous masks — a run of ones followed by a run of zeros — which is exactly what CIDR slash notation `/n` encodes and enforces by construction, since it can only express contiguous prefixes.
How does subnetting relate to the address exhaustion problem that motivated IPv6?
Subnetting made allocation more efficient by letting organizations take only as many addresses as needed rather than an entire class, but it does not increase the total pool — IPv4 still has only about 4.3 billion (2^32) addresses. Efficient subnetting and later CIDR aggregation delayed exhaustion by years by eliminating waste, and NAT further stretched the remaining public address supply by letting whole private subnets share one public address. But none of these techniques create new addresses; they only use the existing 32-bit space more economically. IPv6's 128-bit addresses solve the underlying scarcity permanently, while reusing the same conceptual network/host partitioning and hierarchical routing philosophy that subnetting established for IPv4.
If I have a /26 subnet, how do I quickly find its broadcast address without full binary conversion?
A fast mental shortcut: a `/26` mask leaves 6 host bits, so each subnet spans `2^6 = 64` addresses, meaning subnet boundaries always fall on multiples of 64 in whichever octet the split occurs. Find the multiple of 64 at or below your address in that octet — that's the network address — then add 63 to get the broadcast address (since the range spans 64 consecutive values, indices 0 through 63). For example, `10.0.0.100/26`: 100 falls between 64 and 128, so the network address is `10.0.0.64` and the broadcast is `10.0.0.64 + 63 = 10.0.0.127`. This block-size trick — subnet size `2^h`, boundaries at multiples of that size — works for any prefix length and avoids manual binary arithmetic entirely.
Why does longest prefix match matter instead of just picking any matching route?
Multiple routes can legitimately match the same destination address simultaneously, because subnetting is hierarchical: a `/24` route for one specific subnet and a `/16` aggregate route covering that same subnet plus many others can both match. If a router picked the coarser `/16` route, traffic destined for a specific, more precisely known subnet would be misdirected toward a generic gateway that may not have the most efficient or even correct path to that particular subnet. Longest prefix match resolves this by always preferring the most specific (longest) matching prefix, which corresponds to the most detailed routing information available — exactly the property that lets networks advertise both broad aggregates and specific exceptions within them, a mechanism fully generalized in CIDR and Hierarchical Address Aggregation.
Does subnetting affect performance, or is it purely an organizational/addressing convenience?
It affects performance meaningfully. Smaller subnets mean smaller broadcast domains, which directly reduces the volume of ARP requests, DHCP broadcasts, and other flood traffic that every host on the segment must process, improving effective throughput and reducing CPU overhead on end devices. Subnetting also enables traffic isolation and access control at the router/firewall boundary, so problems (broadcast storms, misbehaving devices, security incidents) in one subnet don't directly saturate or expose another. On the routing side, well-planned hierarchical subnetting is what allows route aggregation, keeping backbone routing tables from growing linearly with the number of individual networks — a scalability property that is central to how the modern internet's routing infrastructure remains tractable.