Ch. 20 · Networking

Networking: IP Subnetting and CIDR

Work out network, broadcast and usable hosts for any CIDR block by hand, check it with Python, and lay out a VPC that won't need renumbering.

~5 min readintermediateupdated Oct 19, 2026

Subnetting questions show up in two forms. One is arithmetic: “what range does 10.20.37.140/20 belong to?” The other is design: “carve a VPC for three availability zones without boxing yourself in.” The arithmetic is quick once you see the pattern, and the design question is where interviewers find out whether you’ve actually run out of addresses before.

What the slash number means

An IPv4 address is 32 bits, written as four 8-bit octets. CIDR notation, like 10.20.0.0/16, says the first 16 bits are the network prefix and the remaining 16 identify hosts inside it. So the block has 2^16 = 65,536 addresses, from 10.20.0.0 to 10.20.255.255.

The same thing written as a subnet mask is the prefix as ones followed by zeros: /16 is 255.255.0.0, /20 is twenty ones, which is 255.255.240.0, and /28 is 255.255.255.240.

Two rules cover most questions:

  • A /n block contains 2^(32 − n) addresses. Each extra bit of prefix halves the block: /24 has 256, /25 has 128, /28 has 16.
  • A block always starts on a multiple of its own size. A /20 holds 4,096 addresses, which is 16 values of the third octet, so /20 networks start at third octets 0, 16, 32, 48 and so on. 10.20.37.0/20 is not a valid network address, because 37 isn’t a multiple of 16.

Working one out by hand: 10.20.37.140/20

The quick method: find the octet where the mask stops being 255. For /20 that’s the third octet, with mask byte 240.

  1. Block size in that octet is 256 − 240 = 16.
  2. Network start: round the address’s third octet (37) down to a multiple of 16, giving 32. So the network is 10.20.32.0/20.
  3. Broadcast: the next block starts at 48, so this one ends just before it, at 10.20.47.255.
  4. Usable hosts: on a traditional LAN, the first address names the network and the last is broadcast, so hosts run from 10.20.32.1 to 10.20.47.254: 4,094 of them.

Step 2 is really a bitwise AND, and seeing it in binary explains why the shortcut works:

37   = 0010 0101
240  = 1111 0000
AND  = 0010 0000  = 32
Text

The mask keeps the high four bits of the octet (network part) and zeroes the low four (host part). Rounding down to a multiple of 16 is the same operation.

Python’s standard ipaddress module will check your work, which is a handy habit when you’re writing infrastructure code:

import ipaddress as ip

host = ip.ip_interface("10.20.37.140/20")
print(host.network, host.network.broadcast_address, host.netmask)

for s in ["10.20.0.0/24", "10.20.0.0/28", "10.20.0.0/31", "10.20.0.0/32"]:
    net = ip.ip_network(s)
    hosts = list(net.hosts())
    print(net, net.num_addresses, len(hosts), hosts[0], hosts[-1])

ip.ip_network("10.20.37.0/20")
python

Output:

10.20.32.0/20 10.20.47.255 255.255.240.0
10.20.0.0/24 256 254 10.20.0.1 10.20.0.254
10.20.0.0/28 16 14 10.20.0.1 10.20.0.14
10.20.0.0/31 2 2 10.20.0.0 10.20.0.1
10.20.0.0/32 1 1 10.20.0.0 10.20.0.0
ValueError: 10.20.37.0/20 has host bits set
Text

Two edge cases appear in that output. A /31 has two usable addresses and no broadcast, because RFC 3021 allows it for point-to-point links where a broadcast address is pointless. A /32 is a single host, which is how you write one address in a route table or a firewall rule. And the ValueError is the module enforcing the “starts on a multiple of its size” rule. (Pass strict=False if you want it to round down for you.)

Cloud subnets don’t give you every address

Interviewers who’ve worked on AWS like this one. In a VPC subnet, AWS reserves five addresses, not two: the network address, .1 for the VPC router, .2 for the DNS resolver, .3 held for future use, and the last address. A /24 gives you 251 usable addresses, and the smallest subnet AWS allows, a /28, gives you 11. Azure also reserves five per subnet; GCP reserves four. The exact list differs, but “you lose more than two” is the point.

That matters when subnets are small. A /28 for a NAT gateway is fine. A /24 for an EKS node group using the AWS VPC CNI is not, because that plugin gives every pod a real VPC address. A dozen nodes running 30 pods each needs well over 251 addresses, and the symptom is pods stuck in ContainerCreating with IP allocation errors rather than anything that says “subnet full”.

Designing a VPC that you won’t have to renumber

Suppose you’re given 10.20.0.0/16 for a production VPC across three availability zones, with public subnets for load balancers, private subnets for application nodes and pods, and isolated subnets for databases.

A layout that holds up:

private-a   10.20.0.0/20    4,091 usable on AWS
private-b   10.20.16.0/20
private-c   10.20.32.0/20
public-a    10.20.48.0/24   251 usable
public-b    10.20.49.0/24
public-c    10.20.50.0/24
data-a      10.20.52.0/24
data-b      10.20.53.0/24
data-c      10.20.54.0/24
(free)      10.20.64.0/18   16,384 addresses held back
Text

The decisions behind it:

  • Big private subnets. That’s where node and pod counts grow. A /20 per zone is generous now and still fine after the cluster triples.
  • Small public subnets. Only load balancers and NAT gateways live there.
  • Aligned, contiguous blocks. Every allocated subnet sits inside 10.20.0.0/18, so one route or security rule can cover them all, and the first two private subnets merge into a single /19. Python’s ip.collapse_addresses on 10.20.0.0/20 and 10.20.16.0/20 returns 10.20.0.0/19: adjacent aligned blocks merge cleanly.
  • A reserved quarter. You can add a fourth zone or a new tier later without touching existing subnets. Most cloud providers won’t let you resize a subnet in place, so “we’ll make it bigger later” means migrating workloads.

The biggest design mistake isn’t inside the VPC at all. It’s picking a range that overlaps with something you’ll need to reach. Two VPCs that both use 10.0.0.0/16 can’t be peered, because a packet for 10.0.5.9 has no unambiguous destination. The same goes for the office VPN, a partner’s network, or the default Docker bridge (172.17.0.0/16), which regularly collides with corporate networks in the 172.16.0.0/12 private range and makes a laptop lose access to an internal service when Docker starts. Keep an organisation-wide allocation list and hand out blocks from it.

Interview exercise: will this new subnet fit?

The VPC above needs an extra subnet for a batch workload in zone b. A teammate proposes 10.20.24.0/22, “since there’s room after private-b”. Is it valid, and if not, what would you pick?

Answer

It overlaps. private-b is 10.20.16.0/20, whose block covers third octets 16 through 31 (16 + 16 − 1). 10.20.24.0/22 covers third octets 24 to 27, entirely inside private-b. The cloud API would reject it with an overlap error, and Python agrees:

ip.ip_network("10.20.16.0/20").overlaps(ip.ip_network("10.20.24.0/22"))  # True
ip.ip_network("10.20.16.0/20").overlaps(ip.ip_network("10.20.64.0/22"))  # False
python

The confusion comes from treating the subnet as if it ended where its current hosts end. A subnet owns its whole block whether or not addresses are in use. I’d take the new subnet from the reserved range, for example 10.20.64.0/22, which is aligned (64 is a multiple of 4) and doesn’t overlap anything. If the workload is large and long-lived, I’d give it a /20 from the same reserve so it doesn’t need to move later.

More on this topic

More in Networking

read ✓Networking · mid

Networking: DNS Caching and TTL

See how resolvers count TTLs down, why NXDOMAIN answers are cached too, and how to schedule a DNS cutover so stale answers expire.

~7 min readread →
read ✓Networking · easy

Networking: DNS Record Types

Read real dig output for A, AAAA, CNAME, MX, TXT, NS, SOA and CAA records, and learn which combinations DNS rejects and why.

~6 min readread →
read ✓Networking · mid

Networking: HTTP Redirects

Test 301, 302, 303, 307 and 308 against a local Node server with curl and fetch, and see which codes turn a POST into a GET and why.

~6 min readread →
esc