This page is the "throw everything at it" drill for File allocation — contiguous, linked, indexed (inode) . We will not learn new theory — we will exercise the three schemes across every kind of input they can face: normal cases, the boundary between two index levels, a zero-size file , a file that is exactly one block , a growth request that fails, and an exam twist. Before any example, we build a map of all the cases.
Think of file allocation as a machine with a lever set to one of three positions — contiguous , linked , or indexed . For each position, the same handful of questions come up: where is byte N? how many reads? does it fit? can it grow? The scenario matrix below is the checklist of every (lever × question) combination that an exam can hand you. If we work one example per surprising cell, no case is left unseen.
Two numbers appear in almost every example, so we pin them down once, in plain words:
B , pointer size p , and file length L
B = the number of bytes in one disk block . A disk is a shelf of equal boxes; B is how many bytes fit in one box. We use B = 4096 (4 KB) throughout.
p = the number of bytes needed to write down one block address . To point at a box you write its number; p is how many bytes that number takes. We use p = 4 .
k = B / p = pointers per block = how many addresses fit inside one whole block. Here k = 4096/4 = 1024 .
L = the number of blocks the file occupies (its length measured in whole blocks, not bytes). A 0-block file has L = 0 ; a one-block file has L = 1 .
Definition Two symbols used in the counting examples
≈ means "approximately equal to" . We write it when we round a big or messy number to a friendlier one, e.g. 49 , 152 B ≈ 48 KB — the two are close , not exactly equal.
⌈ x ⌉ is the ceiling : round up to the next whole number. Why up? If a file needs 732.4 blocks of storage, you cannot buy 0.4 of a block — you must grab a whole 733rd block for the leftover bytes. So ⌈ 732.4 ⌉ = 733 . (Contrast with floor ⌊ ⋅ ⌋ , which rounds down .)
Every cell below is a distinct situation. The Ex column says which worked example covers it.
Case class
Contiguous
Linked
Indexed (inode)
Normal byte-to-block lookup
Ex 1
Ex 3
Ex 5
Boundary between levels/blocks
Ex 2 (block edge)
—
Ex 6 (direct↔indirect edge), Ex 12 (single↔double↔triple)
Zero-size / degenerate input
Ex 8 (empty & 1-block)
Ex 8
Ex 8
Limiting value (max reach)
—
Ex 11 (pointer-width limit)
Ex 7 (max file size)
Growth request (can it grow?)
Ex 4 (fails)
Ex 4 (succeeds)
Ex 4 (succeeds)
Real-world word problem
—
Ex 3
Ex 9 (photo library)
Exam-style twist
—
Ex 11 (width vs count)
Ex 10 (reads for 3 blocks)
Related boundaries live in External vs Internal Fragmentation (Ex 4), Free Space Management (bitmap, free list) (Ex 4 growth), and File Systems (ext4, FAT, NTFS) (Ex 3 FAT note).
The picture below (Figure s01 ) shows bytes as a long ruler and blocks as the tick-marks every B bytes; the amber arrow marks byte 9000 landing inside block 2 at offset 808.
A file starts at physical block b = 50 , block size B = 4096 . Which physical block and offset hold byte 9000 ?
Forecast: guess before reading — is it block 51 or 52? Roughly which offset?
i = ⌊ 9000/4096 ⌋ = 2 . Why this step? Bytes 0–4095 fill block 0, 4096–8191 fill block 1, so 9000 lands in the third logical block, index 2. This is the ruler from Figure s01.
offset = 9000 mod 4096 = 9000 − 2 ⋅ 4096 = 808 . Why? After two whole blocks (8192 bytes) we are 808 bytes into the next.
phys = b + i = 50 + 2 = 52 . Why? Contiguous means logical block i sits at b + i — pure arithmetic, no chain, O ( 1 ) .
Verify: physical byte = 52 ⋅ 4096 + 808 = 213 , 800 . And b ⋅ B + N = 50 ⋅ 4096 + 9000 = 213 , 800 . ✓ Same byte from both routes.
Same file (b = 50 ). Compare byte 4095 and byte 4096 — do they share a block?
Forecast: they differ by just one byte. Same block, or a jump?
Byte 4095: i = ⌊ 4095/4096 ⌋ = 0 , offset = 4095 . Why? Still inside block 0, last byte of it.
Byte 4096: i = ⌊ 4096/4096 ⌋ = 1 , offset = 0 . Why? Exactly one block of bytes has passed; we roll into block 1 at offset 0.
phys: byte 4095 → block 50 + 0 = 50 ; byte 4096 → block 50 + 1 = 51 . Why? The tiny +1 in byte crosses a block boundary — this is exactly where floor "ticks over".
Verify: offsets 4095 and 0 are the max and min of a block's range [ 0 , B − 1 ] ; adding 1 byte must reset offset and increment i . ✓
A log file is stored with linked allocation ; its blocks on disk are the chain 7 → 3 → 90 → 12 . The directory only knows the first block, 7. A program wants logical block 3 (its data). How many disk reads to locate it, and how many usable data bytes per block if p = 4 ?
Forecast: guess the read count — is it 1 (like contiguous) or 4?
Read block 7; its stored pointer says "next = 3". Why? Linked allocation hides the "next" address inside the block — you cannot know block 1's location without reading block 0.
Read block 3 → next = 90; read block 90 → next = 12; read block 12 = the target. Why? No formula exists; each address is revealed only by the previous read. That's the O ( n ) cost.
Total reads to reach logical block i is i + 1 = 3 + 1 = 4 . Why? You touch blocks 0 , 1 , 2 , 3 .
Usable data per block = B − p = 4096 − 4 = 4092 bytes. Why? The 4-byte pointer squats inside the block, stealing data space (this is internal overhead ).
Verify: the FAT fix moves pointers into RAM, so chain-walking costs 0 disk reads and usable data returns to full B = 4096 . Consistency check: 4092 = 4096 − 4 . ✓
The chain and its four reads are drawn in Figure s02 : each cyan box is a data block, the amber slot inside it is the next-pointer, and the amber arrows are the links you must follow one after another to reach logical block 3.
A 3-block file sits at contiguous blocks 20–22. Block 23 is already occupied by another file. The user appends data needing a 4th block . What happens under each scheme?
Forecast: which scheme(s) grow in place, and which must copy the whole file?
Contiguous: block 23 is taken, so the file cannot extend. It must be moved entirely to a free run of ≥4 blocks (say 60–63) and blocks 20–22 freed. Why? Contiguous demands consecutive blocks — no gap allowed. Cost = copy all existing data. If no run of 4 exists despite enough total free space, that is external fragmentation .
Linked: grab any free block, say block 200; write the old last block's pointer to point at 200. Why? A chain doesn't care where the next link lives. Growth = O ( 1 ) , no copy.
Indexed (inode): grab any free block 200; write its address into the next empty index slot. Why? The index is just an array of addresses; appending is one slot write, no copy. Free block found via the free-space bitmap .
Verify: matches the comparison table — "Grows easily?" is No / Yes / Yes for contiguous / linked / indexed. ✓
An indexed file's index block is Index = [50, 51, 90, 12, ...]. Find the physical block and offset for byte 9000 (B = 4096 ).
Forecast: how many disk reads after the index is loaded — 3 like linked, or 1?
i = ⌊ 9000/4096 ⌋ = 2 , offset = 9000 mod 4096 = 808 . Why? Same byte-splitting ruler as always (Figure s01).
phys = Index [ 2 ] = 90 . Why? The index is a direct lookup table — entry i is the address. One array read, O ( 1 ) , no chain.
Read physical block 90, return byte at offset 808.
Verify: compare to Ex 3 — linked needed 3 reads to reach logical block 2; indexed needs 1 index lookup. Same file layout, huge speed difference. ✓
Unix inode: 12 direct pointers (logical blocks 0–11), then a single-indirect block holding k = 1024 pointers (logical blocks 12–1035). For logical blocks 11 and 12 , how many disk reads to fetch the data (inode already in memory)?
Forecast: does crossing from 11 to 12 cost an extra read?
Block 11: it is a direct pointer — the inode already holds its address. Reads = 1 (just the data block). Why? Direct pointers live in the inode, which is in memory; zero pointer-blocks to fetch.
Block 12: first block past the 12 direct slots → it lives via the single-indirect block. Reads = 2 : (1) read the single-indirect block to get the address, (2) read the data block. Why? One extra level of pointers must be dereferenced from disk.
The jump 11→12 adds exactly one disk read. Why? That is the price of leaving the direct region — the "80/20" design keeps tiny files (≤12 blocks) at 1 read.
Verify: direct region size = 12 blocks (indices 0..11); single-indirect adds k = 1024 (indices 12..1035). 12 + 1024 = 1036 blocks reachable before double-indirect. ✓
Figure s03 draws exactly this boundary: the in-memory inode on the left, a cyan single-read arrow to data block 11 (direct), and an amber two-hop path (indirect block → data block) to block 12.
With B = 4096 , p = 4 so k = 1024 , compute the maximum file size an inode can address.
Forecast: is it megabytes, gigabytes, or terabytes? Which level dominates?
Direct: 12 × B = 12 × 4096 = 49 , 152 B ≈ 48 KB (recall ≈ means "close to, after rounding"). Why? 12 pointers, one block each.
Single-indirect: k × B = 1024 × 4096 = 4 , 194 , 304 B = 4 MB. Why? One block of k pointers, each a data block.
Double-indirect: k 2 × B = 102 4 2 × 4096 = 4 , 294 , 967 , 296 B = 4 GB. Why? k pointers, each to a single-indirect block of k → k 2 data blocks.
Triple-indirect: k 3 × B = 102 4 3 × 4096 = 4 , 398 , 046 , 511 , 104 B ≈ 4 TB. Why? One more factor of k .
Total = 49 , 152 + 4 , 194 , 304 + 4 , 294 , 967 , 296 + 4 , 398 , 046 , 511 , 104 = 4 , 402 , 345 , 721 , 856 B ≈ 4 TB. Why? Each level multiplies reach by k = 1024 , so triple-indirect swamps the rest.
Verify: total in TiB = 4 , 402 , 345 , 721 , 856/102 4 4 ≈ 4.004 TiB — dominated by the triple term, exactly as the geometric growth predicts. ✓
Handle the two smallest cases in each scheme: a 0-byte file (length L = 0 blocks) and a 1-block (4096-byte) file (length L = 1 block).
Forecast: does a 0-byte file still need an index block? Does it use any data blocks?
0-byte file, all schemes: uses 0 data blocks , i.e. L = 0 . Contiguous stores ( b , L = 0 ) ; linked stores "first block = none"; inode stores metadata with all pointers null . Why? ⌈ 0/ B ⌉ = 0 blocks of data (ceiling of 0 is 0) — there is nothing to point at. The inode still exists (name, permissions), just empty.
1-block file, contiguous: L = 1 , byte N with 0 ≤ N < 4096 gives i = ⌊ N / B ⌋ = 0 , phys = b . Why? Only block 0 exists; every valid byte maps to it.
1-block file, linked: directory's first block is the only block; usable data = B − p = 4092 , so a full 4096-byte file actually needs ⌈ 4096/4092 ⌉ = 2 linked blocks (4092 in the first, 4 in the second). Why? The in-block pointer shrinks capacity — a subtle degeneracy the ceiling catches!
1-block file, inode: direct pointer 0 holds the block; reads to fetch it = 1 . Why? It fits in the direct region, zero indirection.
Verify: blocks needed for 4096 data bytes = ⌈ 4096/ B ⌉ = 1 for contiguous/indexed (full block usable), but ⌈ 4096/ ( B − p )⌉ = ⌈ 4096/4092 ⌉ = 2 for plain linked. ✓
A phone stores 6,000 photos, each 3 MB, in one directory using inodes (B = 4096 , k = 1024 ). (a) How many data blocks does one photo use? (b) Which inode region holds a photo's blocks — direct, single, or double? (c) Reads to fetch a photo's last block?
Forecast: 3 MB — will 12 direct pointers be enough?
Blocks per photo = ⌈ 3 , 000 , 000/4096 ⌉ = ⌈ 732.4 ⌉ = 733 blocks (ceiling rounds the partial 733rd block up to a whole block). Why? 732 × 4096 = 2 , 998 , 272 < 3 , 000 , 000 , so a 733rd partial block is needed.
Region: direct covers 0–11 (12 blocks), single-indirect covers 12–1035. Since 733 ≤ 1035 , the photo fits within direct + single-indirect — the last logical block index is 733 − 1 = 732 , which is ≥ 12 , so it lives in the single-indirect region. Why? 732 is past the 12 direct slots but under 1036.
Reads for the last block: (1) read single-indirect block, (2) read the data block = 2 reads. Why? One level of indirection, inode already in memory.
Verify: 732 × 4096 = 2 , 998 , 272 bytes covered by 732 blocks; the 733rd block holds the remaining 3 , 000 , 000 − 2 , 998 , 272 = 1 , 728 bytes. And 12 ≤ 732 ≤ 1035 ⇒ single-indirect. ✓
Inode, k = 1024 . A program reads logical blocks 10, 11, 12 in sequence. Assuming no caching of pointer blocks between reads, count total disk reads. Then say what changes with caching.
Forecast: is it 1 + 1 + 2 = 4 , or does block 12 cost more?
Block 10 → direct: 1 read (data only). Block 11 → direct: 1 read. Why? Both indices are below 12, so they sit in the 12 direct slots held by the in-memory inode; no pointer-block to fetch.
Block 12 → single-indirect: read the single-indirect block (1 ) + the data block (1 ) = 2 reads. Why? Index 12 is the first past the direct region, so one pointer-block must be dereferenced from disk before the data — exactly the boundary of Ex 6.
No-cache total = 1 + 1 + 2 = 4 reads. Why? With no caching each fetch is independent, so we simply add the per-block costs.
With caching: the single-indirect block, once read for block 12, stays in memory; blocks 13, 14, … then cost only 1 read each (their addresses are already in the cached pointer block). Why? The pointer block is reused instead of re-read — this is why real filesystems cache indirect blocks aggressively, turning a sequential scan into one pointer-block read plus one read per data block.
Verify: without cache, 3 blocks cost 4 reads; block 12 is the only one crossing into indirection (index 12 ≥ 12 ). Direct blocks (indices < 12 ) cost 1 each ⇒ 1 + 1 + 2 = 4 . ✓
In plain linked allocation the "next block" address is p = 4 bytes wide. (a) What is the largest disk (in blocks, then in bytes) that a 4-byte pointer can even name ? (b) What does that cap the maximum linked file at? (c) Twist: why is this ceiling a different kind of limit from the inode ceiling in Ex 7?
Forecast: linked files can grow one block at a time forever — so is there any size ceiling at all?
A p = 4 -byte pointer is 4 × 8 = 32 bits, so it can hold at most 2 32 distinct block numbers. Why? Each bit doubles the count of addresses; 32 bits name 2 32 = 4 , 294 , 967 , 296 blocks. This is the true ceiling — not the chain length, but the width of the pointer that stores the chain .
Total addressable disk = 2 32 × B = 4 , 294 , 967 , 296 × 4096 = 17 , 592 , 186 , 044 , 416 B = 16 TiB. Why? Every block the pointer can name is B bytes; multiply.
A single linked file can be no larger than the whole addressable disk, so its cap is also ≈ 16 TiB (minus the pointer overhead p eaten from every block, and minus space other files use). Why? A file is a subset of the disk's blocks; you cannot chain to a block whose number the pointer cannot express.
Twist answer: the linked limit comes from pointer width (how big a single block number can be), while the inode limit in Ex 7 comes from pointer count across indirection levels (how many pointers you can arrange). Widen the pointer to 8 bytes and the linked disk jumps to 2 64 × B ; the inode instead grows by adding indirection levels . Why does this matter? On an exam, "increase max file size" has two totally different answers depending on scheme.
Verify: 2 32 = 4 , 294 , 967 , 296 blocks; × 4096 = 17 , 592 , 186 , 044 , 416 B; dividing by 102 4 4 gives exactly 16 TiB. ✓
Inode, k = 1024 , all pointer blocks uncached (inode itself in memory). Find the disk reads to fetch the first logical block of each region : the last single-indirect block, the first double-indirect block, the last double-indirect block, and the first triple-indirect block.
Forecast: each region crossing should add exactly one more pointer-block read. True?
Region indices (built from Ex 6/7): direct = 0 … 11 ; single = 12 … 1035 (that's 12 + k − 1 ); double = 1036 … ( 1036 + k 2 − 1 ) = 1036 … 1 , 049 , 611 ; triple starts at 1 , 049 , 612 . Why? Single adds k blocks, double adds k 2 , so each region's last index is the previous last plus its span.
Last single-indirect block (index 1035): read single-indirect block (1) + data (1) = 2 reads . Why? One pointer level.
First double-indirect block (index 1036): read the double-indirect block (1) → read the single-indirect block it points to (1) → read data (1) = 3 reads . Why? Two pointer levels: the double block lists single-indirect blocks, so we dereference twice.
Last double-indirect block (index 1,049,611): still 3 reads — same two-level path, just the last slot of the last single-indirect block under the double. Why? Staying inside the double region never changes the level count.
First triple-indirect block (index 1,049,612): read triple block (1) → double block (1) → single block (1) → data (1) = 4 reads . Why? Three pointer levels stack up.
Verify: reads per region = 2, 3, 3, 4 — each region boundary (single→double, double→triple) adds exactly one pointer-block read, matching "one extra level = one extra read". Region spans: single = 1024 , double = 102 4 2 = 1 , 048 , 576 ; last double index = 1036 + 1 , 048 , 576 − 1 = 1 , 049 , 611 ; triple starts 1 , 049 , 612 . ✓
Figure s04 stacks the four regions as nested pointer-block ladders; the amber count beside each shows reads climbing 1 → 2 → 3 → 4 as you descend into deeper indirection.
Recall Quick self-test
Contiguous phys of byte 9000, start block 50, B=4096? ::: block 52, offset 808.
Linked: reads to reach logical block 3 of chain 7→3→90→12? ::: 4 reads.
Inode with B=4096, p=4, k=1024 — max file size? ::: about 4 TB (49152 + 4 MB + 4 GB + 4 TB bytes).
Reads to fetch logical block 12 in an inode (inode in RAM)? ::: 2 (single-indirect block + data block).
Reads to fetch the first triple-indirect block (uncached)? ::: 4 (triple + double + single + data).
Blocks a 3 MB photo needs at B=4096? ::: 733 blocks.
Does a 0-byte file need any data blocks? ::: No — all pointers null, but the inode still exists.
Largest disk a 4-byte linked pointer can name? ::: 2^32 blocks × 4096 B = 16 TiB.
"D-S-D-T" for inode reach multipliers: D irect ×12, then S ingle ×k, D ouble ×k², T riple ×k³ — each letter after Direct adds one power of k . The read count to reach a block equals the number of these letters you pass through (Direct=1, Single=2, Double=3, Triple=4).