Build the mental model
Subnetting is taking one network and splitting it into several smaller ones by borrowing bits from the host portion and giving them to the network portion. A /24 has 8 host bits; borrow 2 and you get a /26 — four networks of 64 addresses each instead of one network of 256.
There are three separate reasons to do it, and they are often confused. The first is broadcast containment: a broadcast reaches every host in its subnet and is processed by every one of them, so a flat network of thousands of machines burns real CPU and bandwidth on traffic almost nobody wanted. The second is isolation and policy — traffic between subnets must pass through a router, and a router is exactly where you can apply filtering, so putting printers, servers, and guest Wi-Fi in separate subnets gives you somewhere to enforce rules. The third is address efficiency: a point-to-point link between two routers needs exactly two addresses, and handing it a whole /24 wastes 254 of them.
Inside each subnet, two addresses are reserved. The all-zeros host is the network address, the name of the subnet itself; the all-ones host is the directed broadcast address. Neither can be assigned to a machine, and that is exactly where 2^n - 2 comes from: n host bits give 2^n addresses, minus those two. The formula has exceptions. RFC 3021 lets a /31 use both of its addresses on point-to-point links, because a two-host link has no use for a broadcast, and a /32 is a single host route. So do not memorize "a /24 has 254 hosts" — compute it from the actual prefix every time.
SPLITTING 192.168.1.0/24 INTO FOUR /26 SUBNETS
----------------------------------------------
BEFORE: one /24 -- 256 addresses, 254 usable, ONE broadcast domain
+-------------------------------------------------------------+
| 192.168.1.0/24 |
| .0 = network .1 - .254 usable .255 = bcast |
+-------------------------------------------------------------+
Borrow 2 host bits (8 host bits -> 6), giving 2^2 = 4 subnets:
AFTER:
+--------------+--------------+--------------+--------------+
| .0 - .63 | .64 - .127 | .128 - .191 | .192 - .255 |
| /26 | /26 | /26 | /26 |
+--------------+--------------+--------------+--------------+
network .0 network .64 network .128 network .192
first .1 first .65 first .129 first .193
last .62 last .126 last .190 last .254
bcast .63 bcast .127 bcast .191 bcast .255
usable 62 usable 62 usable 62 usable 62
Each block loses 2 addresses (network id + broadcast), so
4 x 62 = 248 usable -- six fewer than the original 254.Connect it to a real scenario
Say you are given 192.168.1.0/24 for a small office and asked to separate staff machines, servers, and guest Wi-Fi. Splitting into four /26 blocks gives you 192.168.1.0/26, .64/26, .128/26, and .192/26, each holding 64 addresses. Assign staff the first, servers the second, guests the third, and keep the fourth for growth.
Now work through one block carefully, because this is exactly where mistakes happen. In 192.168.1.64/26, the network address is 192.168.1.64 and the broadcast address is 192.168.1.127. The usable hosts are 192.168.1.65 through 192.168.1.126 — 62 of them. A common bug is assigning .64 or .127 to a machine because they "look free": .64 will be rejected or behave strangely, and .127 will make every host in the subnet process traffic aimed at it. The block boundaries move with every subnet, which is why .64 is a network address here but a perfectly ordinary host address in a plain /24.
Notice the arithmetic cost too. One /24 gave 254 usable addresses; four /26s give 4 x 62 = 248. Subnetting always loses two addresses per subnet, so splitting aggressively into many tiny subnets burns address space fast. The code below computes all four ranges, then shows how the usable count falls as the prefix grows — including the /31 exception.
Try the working example
import ipaddress
parent = ipaddress.ip_network("192.168.1.0/24")
print("parent %s: %d addresses, %d usable"
% (parent, parent.num_addresses, parent.num_addresses - 2))
print()
print("%-17s%-14s%-15s%s"
% ("subnet", "network", "broadcast", "usable range"))
print("-" * 72)
for sub in parent.subnets(new_prefix=26):
hosts = list(sub.hosts())
print("%-17s%-14s%-15s%s - %s"
% (sub, sub.network_address, sub.broadcast_address,
hosts[0], hosts[-1]))
print()
# Why "2^n - 2": the all-zeros host is the network id and the
# all-ones host is the broadcast address, so neither is assignable.
for prefix in (24, 26, 28, 30, 31, 32):
net = ipaddress.ip_network("10.0.0.0/%d" % prefix)
usable = len(list(net.hosts()))
print("/%-3d %5d addresses -> %5d usable" % (prefix, net.num_addresses,
usable))
parent 192.168.1.0/24: 256 addresses, 254 usable
subnet network broadcast usable range
------------------------------------------------------------------------
192.168.1.0/26 192.168.1.0 192.168.1.63 192.168.1.1 - 192.168.1.62
192.168.1.64/26 192.168.1.64 192.168.1.127 192.168.1.65 - 192.168.1.126
192.168.1.128/26 192.168.1.128 192.168.1.191 192.168.1.129 - 192.168.1.190
192.168.1.192/26 192.168.1.192 192.168.1.255 192.168.1.193 - 192.168.1.254
/24 256 addresses -> 254 usable
/26 64 addresses -> 62 usable
/28 16 addresses -> 14 usable
/30 4 addresses -> 2 usable
/31 2 addresses -> 2 usable
/32 1 addresses -> 1 usable5-minute try-it
Change new_prefix=26 to 28 and see how many subnets you get and how many usable hosts each holds. Then compute how many total usable addresses you lose splitting a /24 into /26s versus into /28s, and compare using /30 versus /31 for router-to-router links.
One important caution
Assuming a /24 always means 254 usable hosts without checking the actual mask — the same arithmetic gives 62 on a /26 and 2 on a /30, so an unchecked assumption corrupts the whole address plan
Assigning a subnet's first address (network id) or last address (broadcast) to a host — .64 is a network address in a /26 but an ordinary host address in a /24, which is why the boundary is easy to miss
RFC 3021 — Using 31-Bit Prefixes on IPv4 Point-to-Point Links — Computer Networking