IP addresses and subnetting basics
45 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)A placement test gives you 192.168.10.77/26 and asks for the network and broadcast address in two minutes. Subnetting looks scary but needs one idea: the address splits into a network part and a host part, and the block size finds the boundary.
IPv4 structure, classes and private ranges
An IPv4 address is a 32-bit number written as four decimal numbers from 0 to 255, separated by dots, such as 192.168.1.10. Each number is an octet (8 bits).
The example classifies an address by its first octet and tells you whether it is private, loopback or public.
Example 1 · Classify an IPv4 address
def class_of(first): if first == 127: return "loopback" for low, high, name in [(1, 126, "class A"), (128, 191, "class B"), (192, 223, "class C"), (224, 239, "class D"), (240, 255, "class E")]: if low <= first <= high: return name return "invalid"
def is_private(a, b): return a == 10 or (a == 172 and 16 <= b <= 31) or (a == 192 and b == 168)
ips = ["10.4.5.6", "172.20.1.1", "172.32.1.1", "192.168.0.10", "8.8.8.8", "127.0.0.1", "230.1.1.1"]for ip in ips: a, b, *_ = map(int, ip.split(".")) kind = "this computer" if a == 127 else "private" if is_private(a, b) else "public" print(f"{ip:<13} {class_of(a):<16} {kind}")10.4.5.6 class A private 172.20.1.1 class B private 172.32.1.1 class B public 192.168.0.10 class C private 8.8.8.8 class A public 127.0.0.1 loopback this computer 230.1.1.1 class D public
What this code does
172.20.1.1is private because 20 is between 16 and 31, but172.32.1.1is public: the 172 private block stops at 172.31.split("\\.")needs the escaped dot becausesplittakes a regular expression, where a plain.means "any character".127.0.0.1is loopback, so it is neither private nor public: it never leaves the computer.
The old classful system set the network size from the first octet. Modern networks use CIDR (next section) instead, but class questions are still common in placement tests.
Classes and special ranges:
- Class A: first octet 1-126, default mask /8. Class B: 128-191, /16. Class C: 192-223, /24.
- Class D: 224-239, multicast (one-to-many). Class E: 240-255, reserved for experiments.
- 127.x.x.x is loopback:
127.0.0.1always means "this computer". - Private ranges (not routed on the internet): 10.0.0.0/8, 172.16.0.0/12 (172.16 to 172.31), 192.168.0.0/16.
Find the bug
Find the wrong range
Each note has one wrong line. Tap it, then check the fix.
A candidate lists the private ranges.
Round 1 of 2CIDR notation and subnet masks
CIDR notation (Classless Inter-Domain Routing) writes an address with a slash and a prefix length, such as 192.168.1.10/24. The prefix says how many of the 32 bits belong to the network.
The example turns a prefix length into a dotted subnet mask by setting that many leading bits to 1.
Example 2 · From prefix length to subnet mask
def to_dotted(value): return ".".join(str((value >> shift) & 255) for shift in (24, 16, 8, 0))
def to_binary(value): bits = f"{value:032b}" return ".".join(bits[i:i + 8] for i in range(0, 32, 8))
def mask_of(prefix): return (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
for p in [8, 16, 20, 24, 26, 27, 30]: print(f"/{p:<2} {to_dotted(mask_of(p)):<16} {to_binary(mask_of(p))}")/8 255.0.0.0 11111111.00000000.00000000.00000000 /16 255.255.0.0 11111111.11111111.00000000.00000000 /20 255.255.240.0 11111111.11111111.11110000.00000000 /24 255.255.255.0 11111111.11111111.11111111.00000000 /26 255.255.255.192 11111111.11111111.11111111.11000000 /27 255.255.255.224 11111111.11111111.11111111.11100000 /30 255.255.255.252 11111111.11111111.11111111.11111100
What this code does
-1is 32 one-bits; shifting it left by32 - prefixleaves exactlyprefixones followed by zeros, which is the mask.- The binary column shows all 32 bits: /20 has four ones in the third octet (
11110000= 240), and /26 ends in11000000(192).
A subnet mask is the same idea written as an address: the first n bits are 1 and the rest are 0. So /24 is 255.255.255.0, /16 is 255.255.0.0 and /26 is 255.255.255.192.
Worked subnetting: network, broadcast and host count
The block size of a subnet is 256 minus the mask value in the interesting octet. Subnets start at multiples of the block size, so you find the block that holds the address and read off network, broadcast and hosts.
The first example is a small subnet calculator. The second splits a college /24 into four lab subnets and prints each range.
Example 3 · A subnet calculator
import ipaddress
def describe(cidr): iface = ipaddress.ip_interface(cidr) net = iface.network first = net.network_address + 1 last = net.broadcast_address - 1 print(cidr) print(f" mask {net.netmask}") print(f" network {net.network_address}") print(f" broadcast {net.broadcast_address}") print(f" hosts {first} - {last} ({net.num_addresses - 2})")
for cidr in ["192.168.10.77/26", "172.16.45.200/20", "10.1.2.3/8"]: describe(cidr)192.168.10.77/26 mask 255.255.255.192 network 192.168.10.64 broadcast 192.168.10.127 hosts 192.168.10.65 - 192.168.10.126 (62) 172.16.45.200/20 mask 255.255.240.0 network 172.16.32.0 broadcast 172.16.47.255 hosts 172.16.32.1 - 172.16.47.254 (4094) 10.1.2.3/8 mask 255.0.0.0 network 10.0.0.0 broadcast 10.255.255.255 hosts 10.0.0.1 - 10.255.255.254 (16777214)
What this code does
ip & maskkeeps the network bits and zeroes the host bits, giving the network address;network | ~masksets every host bit to 1, giving the broadcast address.- The host count uses
longand1L << ...so a /8 (16,777,214 hosts) or even a /1 does not overflow anint. The results match the hand method in the last section.
Example 4 · Splitting a college /24 into four lab subnets
base = "192.168.50."names = ["CSE lab", "ECE lab", "Library", "Hostel"]borrowed = (len(names) - 1).bit_length() # bits needed to count 4 labsprefix = 24 + borrowedblock = 256 >> borrowed
print(f"Borrow {borrowed} bits -> /{prefix}, block size {block}, {block - 2} hosts each")for i, name in enumerate(names): start = i * block print(f"{name:<8} {f'{base}{start}/{prefix}':<18} hosts .{start + 1}-.{start + block - 2} broadcast .{start + block - 1}")Borrow 2 bits -> /26, block size 64, 62 hosts each CSE lab 192.168.50.0/26 hosts .1-.62 broadcast .63 ECE lab 192.168.50.64/26 hosts .65-.126 broadcast .127 Library 192.168.50.128/26 hosts .129-.190 broadcast .191 Hostel 192.168.50.192/26 hosts .193-.254 broadcast .255
What this code does
- Four subnets need 2 borrowed bits (2² = 4), so /24 becomes /26. Each borrowed bit halves the block: 256, 128, 64.
- Each /26 has 64 addresses: 1 network, 1 broadcast and 62 usable hosts. If a lab needed 100 hosts, a /26 would be too small, so always check both counts.
For an address with prefix /n, there are 32 − n host bits. The network address has all host bits 0; the broadcast address (sent to every device on the subnet) has all host bits 1. Neither can be given to a device, so usable hosts = 2^(32 − n) − 2.
For 192.168.10.77/26 the mask octet is 192, so the block size is 64. Subnets start at 0, 64, 128, 192, and 77 falls in the 64 block. Network .64, broadcast .127, hosts .65 to .126, which is 62 hosts.
Order of execution
Put the block-size method in order
You are asked for the network and broadcast of an address. Put the steps in order.
IPv4 vs IPv6
An IPv6 address is 128 bits long, written as eight groups of four hex digits. Leading zeros in a group can be dropped, and one run of all-zero groups can be replaced by ::, so 2001:0db8:0000:0000:0000:0000:0000:0001 becomes 2001:db8::1.
The example expands an IPv6 shorthand back to its full eight groups, so you can check how the :: rule works.
Example 5 · Expanding an IPv6 shorthand
import ipaddress
def expand(short_form): left, _, right = short_form.partition("::") left = left.split(":") if left else [] right = right.split(":") if right else [] groups = left + ["0"] * (8 - len(left) - len(right)) + right return ":".join(g.zfill(4) for g in groups)
for s in ["2001:db8::1", "fe80::1:2", "::1"]: assert expand(s) == ipaddress.IPv6Address(s).exploded # the standard library agrees print(f"{s:<12} -> {expand(s)}")2001:db8::1 -> 2001:0db8:0000:0000:0000:0000:0000:0001 fe80::1:2 -> fe80:0000:0000:0000:0000:0000:0001:0002 ::1 -> 0000:0000:0000:0000:0000:0000:0000:0001
What this code does
::stands for as many zero groups as needed to make eight groups in total. That is why it can appear only once.- Each group is then padded to four hex digits.
::1is the IPv6 loopback address, like127.0.0.1in IPv4.
IPv4 has about 4.3 billion addresses (2³²), which ran out. IPv6 has 2¹²⁸, about 3.4 × 10³⁸. IPv6 also has no broadcast (it uses multicast) and a simpler fixed header, and devices can configure their own addresses.
IPv4 compared with IPv6:
- Size: 32 bits vs 128 bits. Format: dotted decimal vs hex groups with colons.
- IPv4 relies heavily on NAT to share addresses; IPv6 has enough addresses that NAT is rarely needed.
::may appear only once in an address, or the expansion would be ambiguous.
A method for subnetting questions in tests
Most subnetting questions ask for one of five things: the mask, the number of subnets, hosts per subnet, the network address or the broadcast address. Work them in a fixed order so you never mix them up.
Question: 172.16.45.200/20. Find mask, network, broadcast, hosts.1. Mask: /20 = 8+8+4 -> 255.255.240.0 (third octet: 4 ones = 240)2. Block size in the third octet: 256 - 240 = 163. Third octet 45 lies in 32..47 -> network 172.16.32.04. Broadcast: next block 48 minus 1 -> 172.16.47.2555. Hosts: 2^(32-20) - 2 = 4096 - 2 = 4094 Range: 172.16.32.1 to 172.16.47.254When a question asks to split a network into k subnets, borrow b host bits where 2^b ≥ k. Each borrowed bit doubles the subnets and halves the hosts.
Next step
Check your understanding
12 quick questions