Skip to content
AZ Tools

IP Addresses and Subnets, Without the Hand-Waving

Subnetting has a reputation for being fiddly, but it is really one idea applied consistently: an address is a number, and a prefix says how many of its leading bits are fixed. Once that clicks, host counts, overlaps and aggregation all fall out of it. This guide covers the arithmetic, the ranges with special meaning, and the places the intuition breaks.

An address is a number; a prefix is a boundary

An IPv4 address is a single 32-bit number. The familiar dotted form is just that number split into four bytes for human eyes: 192.168.1.10 is 3232235786. Everything a subnet mask does is decide where to cut that number — the leading bits identify the network, the trailing bits identify a host inside it.

CIDR notation writes the cut directly. `/24` means the first 24 bits are the network, leaving 8 bits for hosts; `/26` fixes 26 bits and leaves 6. The mask 255.255.255.0 and the suffix `/24` are the same statement written two ways, and the prefix length is the one worth thinking in — it is a count, so comparisons and containment are just bit arithmetic.

Reading a prefix: how many addresses, and how many usable

A prefix of length *n* in IPv4 contains 2^(32−n) addresses. A `/24` holds 256, a `/26` holds 64, a `/30` holds 4. In a normal broadcast subnet the first address is the network identifier and the last is the broadcast address, so the usable host count is two fewer: 254, 62 and 2 respectively. That last one is why point-to-point links are traditionally `/30`.

The boundaries are not arbitrary: a prefix always starts at a multiple of its own size. There is a `/26` at .0, .64, .128 and .192, and nothing in between — 192.168.1.100/26 really describes the block starting at 192.168.1.64. Two prefixes either nest completely or do not overlap at all, which is what makes routing tables and firewall rules tractable.

The ranges that mean something special

Three IPv4 ranges are reserved for private use and are not routed on the public internet: 10.0.0.0/8, 172.16.0.0/12 (note: not all of 172.x) and 192.168.0.0/16. Alongside them, 127.0.0.0/8 is loopback, 169.254.0.0/16 is link-local — the address a machine gives itself when DHCP fails — and 100.64.0.0/10 is carrier-grade NAT, which is why a device can report a "public" address that still is not reachable.

Knowing these matters for more than trivia. Logs full of 10.x tell you the client address was captured behind a proxy; a 169.254 address means the network never came up; and treating 172.20.x as public because "172 is not private" is a real and recurring mistake. When an allow-list is involved, check the range boundaries rather than the first octet.

IPv6 changes the arithmetic

IPv6 addresses are 128 bits, written as eight groups of four hex digits, with one run of zero groups collapsible to `::` — once per address, otherwise the expansion is ambiguous. Leading zeros in a group are optional, so the same address has many valid spellings and text comparison is useless. Normalise before you compare or deduplicate.

The conventions differ too. A `/64` is the standard size for a single link, so the "how many hosts" question mostly stops being interesting; there is no broadcast address, and no network/broadcast pair to subtract. Prefixes are still nested bit ranges, so aggregation works exactly as it does in IPv4 — only the numbers are much larger.

Sorting, aggregating and comparing

Never sort addresses as strings. Lexicographically, 10.0.0.9 comes after 10.0.0.100 and 9.0.0.1 comes after 10.0.0.1, so any report ordered that way is quietly wrong. Sort by the numeric value — that is what makes adjacent blocks land next to each other, which is the first step in every aggregation.

Aggregation itself is the reverse of splitting: two prefixes of the same length that share every bit but the last one merge into a single prefix one bit shorter. Doing that repeatedly turns a sprawling allow-list into a handful of blocks, which is worth real money in rule limits and lookup time. Just remember that an arbitrary start–end range is not always a single prefix: 10.0.0.5–10.0.0.20 needs several blocks to cover exactly, and rounding it out to a tidy `/27` grants access you did not intend.

  • `/24` = 256 addresses, 254 usable. `/26` = 64, 62 usable. `/30` = 4, 2 usable.
  • A prefix starts at a multiple of its size — 192.168.1.100/26 is the block at .64.
  • Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Link-local: 169.254.0.0/16. CGNAT: 100.64.0.0/10.
  • Compare addresses numerically, never as text.

Related tools