Visual walkthrough — ext4 structure — superblock, block groups, inodes
We build everything from zero. If you have never seen a bit, a byte, or a floor bracket, start at Step 1 and do not skip.
Step 1 — A disk is a flat array of blocks
WHAT. Picture the whole disk as one long row of identical boxes. Each box is a block — a fixed chunk of storage, commonly bytes (that is KiB). We number them .
WHY. The hardware gives us no meaning — no "files", no "free space", just numbered boxes. Every clever thing ext4 does is a way of writing notes about these boxes into some of the boxes themselves. So the first picture is just the naked row.
PICTURE. Look at the row below. The label under a box is its block number. Nothing yet says which boxes are used — that is the very problem we must solve.

Step 2 — The question: "which boxes are free?"
WHAT. Before ext4 can store a file, it must know which blocks are already taken. We need a compact yes/no answer for every block: used () or free ().
WHY a bitmap, not a list? We could keep a list of free block numbers, but a list of numbers is bulky and slow to search. Instead we use a bitmap: one bit per block. Bit answers one tiny question — "is block used?" This is the cheapest possible per-block record: exactly one switch each. (See Bitmaps for allocation for the general technique.)
PICTURE. Below, each block from Step 1 gets a bit sitting on top of it. A magenta bit () = used, a pale bit () = free. The strip of bits is the bitmap.

Step 3 — Where do we store the bitmap? Inside one block.
WHAT. The bitmap is data too, so it must live in blocks. ext4 makes a clean choice: the block bitmap is exactly ONE block.
WHY exactly one? Two reasons, both about simplicity and speed:
- One block is one read — the OS grabs the entire "free map" for a region in a single disk access.
- A fixed-size bitmap means the geometry is predictable: every region looks identical, so
mkfscan place things by arithmetic instead of searching.
PICTURE. We take one block (orange), blow it up, and show it is a grid of bits — because it is bytes and each byte is bits.

Step 4 — The killer constraint → blocks per group
WHAT. Now combine Step 2 and Step 3. The bitmap tracks blocks (needs bits). The bitmap is one block (holds bits). A bit cannot describe a block it has no room for, so:
We take the largest region a single bitmap can fully cover, so we set . That region is a block group.
WHY set it to the maximum? Bigger groups = fewer groups = less repeated metadata and better locality of a file with its own map. But we cannot exceed , because bit number simply does not exist in the block. So the maximum is the sweet spot the constraint hands us for free.
PICTURE. On the left, a bitmap block with bits. Arrows fan out from it, each bit pointing at exactly one data block in a region of blocks on the right. Every arrow lands; none is left over. That region is one block group.

Step 5 — Plug in the real number
WHAT. Substitute the common bytes.
WHY. A formula is abstract; a smart 12-year-old should see the actual megabytes on their own disk.
Now convert that many blocks into bytes:
PICTURE. A single block group drawn to scale as a labelled bar: a thin orange bitmap slice at the front, then a thin inode-bitmap slice, then the inode table, then the wide band of data blocks — the whole bar tagged "128 MiB".

Step 6 — Tiling the disk with groups (and the leftover case)
WHAT. The whole disk is just these -MiB groups laid end to end. For a GiB disk:
WHY the division. Total capacity ÷ group size = how many groups fit. Each numerator/denominator carries the unit MiB, which cancels, leaving a pure count.
EDGE CASE — the disk is not a whole multiple. Real disks rarely divide evenly. If the last group would be partial, ext4 keeps it as a short final group: its bitmap still has bits but the extra bits (for blocks that do not exist) are permanently marked used () so the allocator never hands them out.
PICTURE. Groups G0…G3 shown as equal orange bars, then a shorter final bar whose non-existent tail is hatched and marked "bits forced to 1 — never allocated."

Step 7 — Same idea for inodes → the group-of-an-inode formula
WHAT. Inodes are pre-numbered records (). They are also split evenly across groups: a fixed count of inodes per group. To find which group inode lives in:
WHY subtract 1? Inode numbers are 1-based (they start at ), but positions inside a group's table are 0-based (they start at ). The shifts from counting-from-one to counting-from-zero. The floor bracket means "throw away the fractional part" — it answers "how many whole groups fit before we reach inode ?"
WHY floor and not round? Rounding could push a boundary inode into the next group. Floor never overshoots: it lands you in the group that actually contains .
PICTURE. A number line of inodes cut into groups of . Inode sits inside one segment; a labelled bracket shows counted from the start, and the floor arrow snaps down to the group index.

The one-picture summary
Everything above is one chain: a byte is 8 bits → one block holds bits → one bitmap block can only track blocks → so a block group is blocks → tile those across the disk → number inodes the same way and locate any one with a floor-divide. The figure below compresses that entire logical waterfall into a single flow.

Recall Feynman retelling — say it back in plain words
A disk is just a huge shelf of identical boxes, each box a block of bytes. To use them, ext4 needs a checklist of which boxes are full. It writes that checklist as one bit per box — full or empty — and crams the whole checklist into a single box. But a box only holds bits, so one checklist can only keep track of boxes. That is exactly why a block group is blocks = MiB: the checklist can't watch any more than that. Chop the whole disk into these -MiB chunks; a leftover partial chunk just marks its imaginary boxes as "full" so nobody uses them. Inodes (the little record cards, one per file) are numbered from and split the same even way, so to find which chunk card number lives in you subtract (because cards count from but slots count from ) and divide by the cards-per-chunk, throwing away the fraction. That's the whole geometry — one bitmap box decides the size of everything.
Recall Quick self-test
Blocks per group formula, in words? ::: One bitmap block holds bits, each bit tracks one block, so a group is blocks. Why 128 MiB for 4 KiB blocks? ::: blocks, times bytes MiB. Why the in the inode-group formula? ::: Inodes count from , table slots count from ; the converts between them. What happens to a partial final group? ::: Its bitmap bits for non-existent blocks are forced to (used) so the allocator skips them.
See also: Operating Systems · ext2 vs ext3 vs ext4 · Hard links vs Symbolic links · Virtual File System · Journaling · Disk Scheduling · Memory Management.