Skip to content

File Systems — NTFS, ext4, APFS & ZFS Comparison Guide

DodaTech Updated 2026-06-21 9 min read

In this tutorial, you'll learn about File Systems. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

A file system controls how data is stored, organised, and retrieved on disk. The choice of file system affects performance, reliability, data integrity, and maximum file size — comparing ext4, NTFS, APFS, ZFS, and Btrfs across key dimensions.

What You'll Learn

In this tutorial, you'll learn how modern file systems work: ext4's inode structure and journaling, NTFS Master File Table and B-tree indexing, APFS copy-on-write and snapshots, FAT32/exFAT for portability, Btrfs and ZFS advanced features, the Virtual File System (VFS) abstraction, and hard links vs symbolic links.

Why It Matters

File system corruption means data loss. Understanding how your OS stores files helps you choose the right file system, diagnose disk problems, and recover data. When a database reports "disk full" but there's space — that's likely a file system issue.

Real-World Use

When you take a photo on an iPhone, APFS snapshots the file system instantly. When Windows updates, NTFS transactional NTFS ensures the update either completes or rolls back. Durga Antivirus Pro monitors file system events using inotify to scan new files as they're created.

graph TD
  subgraph "VFS (Virtual File System)"
    VFS[System Call Interface]
  end
  subgraph "File System Implementations"
    EXT4[ext4]
    NTFS[NTFS]
    APFS[APFS]
    BTRFS[Btrfs]
    ZFS[ZFS]
    FAT[FAT32/exFAT]
  end
  subgraph "Block Layer"
    BLK[Block Device Layer]
  end
  subgraph "Storage"
    SSD[SSD / NVMe]
    HDD[HDD]
  end
  VFS --> EXT4
  VFS --> NTFS
  VFS --> APFS
  VFS --> BTRFS
  VFS --> ZFS
  VFS --> FAT
  EXT4 --> BLK
  NTFS --> BLK
  APFS --> BLK
  BLK --> SSD
  BLK --> HDD
â„šī¸ Info

Prerequisites: Python basics. Understanding of Operating Systems fundamentals helps.

ext4 — Extended File System

ext4 is the default file system for most Linux distributions. It uses inodes to store file metadata and extents for block allocation.

Inodes

Each file has an inode containing metadata: permissions, timestamps, owner, size, and pointers to data blocks. The filename is stored separately in the directory entry.

class Ext4Inode:
    def __init__(self, inode_num, mode, size, blocks):
        self.inode_num = inode_num
        self.mode = mode
        self.size = size
        self.blocks = blocks
        self.links_count = 1

    def __repr__(self):
        return f'Inode {self.inode_num}: mode={oct(self.mode)} size={self.size}B blocks={self.blocks}'

class Ext4Directory:
    def __init__(self):
        self.entries = {}

    def create_file(self, name, inode_num, size=0):
        self.entries[name] = inode_num
        return Ext4Inode(inode_num, 0o100644, size, (size + 4095) // 4096)

    def ls(self):
        for name, inode in sorted(self.entries.items(), key=lambda x: x[0]):
            print(f'{name:20s} → inode {inode}')

root = Ext4Directory()
inodes = {}
inodes[1] = root.create_file('readme.txt', 1, 2048)
inodes[2] = root.create_file('script.sh', 2, 512)
inodes[3] = root.create_file('data.csv', 3, 16384)
root.ls()
print(f'\n{inodes[3]}')

Expected output:

data.csv             → inode 3
readme.txt           → inode 1
script.sh            → inode 2

Inode 3: mode=0o100644 size=16384B blocks=4

Journaling

ext4 uses journaling to prevent corruption after crashes. Ordered mode (default) journals metadata and writes data first.

NTFS — New Technology File System

NTFS is the primary file system for Windows. It uses a Master File Table (MFT) — a B-tree of file records. Small files fit entirely within the MFT record (resident data).

class NTFSMFTEntry:
    def __init__(self, record_num, filename, is_directory=False):
        self.record_num = record_num
        self.filename = filename
        self.is_directory = is_directory
        self.attributes = {}

    def add_attribute(self, attr_type, data, resident=True):
        self.attributes[attr_type] = {'type': attr_type, 'resident': resident, 'size': len(data) if isinstance(data, bytes) else data}

    def __repr__(self):
        attrs = ', '.join(self.attributes.keys())
        return f'MFT Entry {self.record_num}: {self.filename} [{attrs}]'

mft = {}
mft[0] = NTFSMFTEntry(0, '$MFT')
mft[0].add_attribute('STANDARD_INFORMATION', '...')
mft[0].add_attribute('FILE_NAME', '$MFT')
mft[5] = NTFSMFTEntry(5, 'document.docx')
mft[5].add_attribute('DATA', 1024 * 1024, resident=False)
mft[6] = NTFSMFTEntry(6, 'notes.txt')
mft[6].add_attribute('DATA', b'Hello, NTFS!', resident=True)
for entry in mft.values():
    print(entry)

Expected output:

MFT Entry 0: $MFT [STANDARD_INFORMATION, FILE_NAME]
MFT Entry 5: document.docx [DATA]
MFT Entry 6: notes.txt [DATA]

APFS — Apple File System

APFS features copy-on-write (CoW): when data is modified, new data is written to a new block. The old block is freed only after all references are removed.

class APFSBlock:
    def __init__(self, block_id, data=b''):
        self.block_id = block_id
        self.data = data
        self.ref_count = 1

class APFSFile:
    def __init__(self, name, data_blocks):
        self.name = name
        self.data_blocks = data_blocks

    def modify(self, offset, new_data, block_allocator):
        old_id = self.data_blocks[offset]
        new_id = block_allocator.allocate(new_data)
        self.data_blocks[offset] = new_id
        block_allocator.decrement_ref(old_id)
        return old_id

class APFSBlockAllocator:
    def __init__(self):
        self.blocks = {}
        self.next_id = 0

    def allocate(self, data):
        block = APFSBlock(self.next_id, data)
        self.blocks[self.next_id] = block
        self.next_id += 1
        return block.block_id

    def decrement_ref(self, block_id):
        self.blocks[block_id].ref_count -= 1
        if self.blocks[block_id].ref_count == 0:
            print(f'  Freed block {block_id}')

allocator = APFSBlockAllocator()
file = APFSFile('document.txt', [
    allocator.allocate(b'Hello World!'),
    allocator.allocate(b'Second block'),
])
print(f'Before: blocks {file.data_blocks}')
old_block = file.modify(0, b'Modified data', allocator)
print(f'After: blocks {file.data_blocks}')
print(f'Old block {old_block} preserved (snapshot reference)')

Expected output:

Before: blocks [0, 1]
After: blocks [2, 1]
Old block 0 preserved (snapshot reference)

Btrfs and ZFS

Feature Btrfs ZFS
Copy-on-write Yes Yes
Snapshots Yes (read/write) Yes (read/write)
Compression lzo, zstd, zlib lz4, gzip, zle
RAID 0, 1, 5, 6, 10 0, 1, 5, 6, 10, mirror, triple
Deduplication Yes Yes
Checksumming CRC-32C Fletcher-4, SHA-256
Max volume size 16 EiB 256 ZiB

VFS — Virtual File System

VFS provides a common interface for all file systems. System calls like open(), read(), write() go through VFS, which dispatches to the specific file system's implementation.

class VFSNode:
    def __init__(self, name, is_directory=False):
        self.name = name
        self.is_directory = is_directory
        self.children = {}
        self.data = b''

    def open(self, path):
        parts = path.strip('/').split('/')
        node = self
        for part in parts:
            if part in node.children:
                node = node.children[part]
            else:
                raise FileNotFoundError(path)
        return node

    def read(self):
        if self.is_directory:
            return list(self.children.keys())
        return self.data

    def write(self, data):
        self.data = data

root = VFSNode('/', is_directory=True)
home = VFSNode('home', is_directory=True)
root.children['home'] = home
readme = VFSNode('readme.txt')
readme.write(b'Hello from VFS!')
home.children['readme.txt'] = readme

node = root.open('/home/readme.txt')
print(f'Opened: /home/{node.name}')
print(f'Content: {node.read()}')

Expected output:

Opened: /home/readme.txt
Content: b'Hello from VFS!'
Feature Hard Link Symbolic Link
Points to Inode Path
Across file systems No Yes
Directory links No (usually) Yes
Orphan if target deleted Data still accessible Broken link
class SimulatedFS:
    def __init__(self):
        self.inodes = {}
        self.dir_entries = {}

    def create_file(self, path, data):
        inode_num = len(self.inodes) + 1
        self.inodes[inode_num] = {'data': data, 'links': 1}
        self.dir_entries[path] = inode_num
        return inode_num

    def hard_link(self, src, dst):
        inode = self.dir_entries[src]
        self.dir_entries[dst] = inode
        self.inodes[inode]['links'] += 1
        print(f'Hard link: {dst} → {src} (inode {inode})')

    def delete(self, path):
        entry = self.dir_entries.get(path)
        if isinstance(entry, int):
            self.inodes[entry]['links'] -= 1
            if self.inodes[entry]['links'] == 0:
                del self.inodes[entry]
                print(f'Inode {entry} freed')
        del self.dir_entries[path]

fs = SimulatedFS()
fs.create_file('/original.txt', b'Hello!')
fs.hard_link('/original.txt', '/hardlink.txt')
print(f'Delete /original.txt')
fs.delete('/original.txt')
print(f'Hard link still works: inode {fs.dir_entries["/hardlink.txt"]}')

Common Mistakes

1. Confusing Inodes and Filenames

An inode stores metadata; the filename is in the directory entry. Multiple filenames (hard links) can point to the same inode.

2. Using FAT32 for Files Larger Than 4GB

FAT32 has a 4GB maximum file size. Use exFAT or NTFS for large files on external drives.

3. Not Considering File System in Database Performance

Databases on ext4 with ordered mode may get better performance than on CoW file systems unless tuned.

4. Running Out of Inodes

A file system with 1M inodes can't create more files even with free space. ext4 reserves enough by default, but small partitions may run out.

5. Ignoring Snapshots on CoW File Systems

ZFS/Btrfs snapshots use space until deleted. Running out of space despite "free" data is often caused by retained snapshots.

Practice Questions

1. What's stored in an ext4 inode vs a directory entry? An inode stores metadata (permissions, timestamps, block pointers). A directory entry maps a filename to an inode number.

2. How does NTFS MFT work? The MFT is a B-tree of file records. Each file has at least one record containing attributes. Small files store data directly in the MFT record (resident data).

3. What is copy-on-write in APFS? When data is modified, APFS writes new data to a new block instead of overwriting. The old block is preserved for snapshots and cloning.

4. What's the difference between VFS and a file system? VFS is the kernel abstraction layer for all file systems. The file system is the specific implementation (ext4, NTFS). VFS provides a common API.

5. Challenge: Implement a simple CoW file system in Python with snapshots. When a snapshot is taken, preserve all referenced data blocks. On modification, allocate new blocks. Free only when no snapshot references a block.

Mini Project: File System Simulator

class SimpleFS:
    def __init__(self, block_size=512):
        self.block_size = block_size
        self.blocks = {}
        self.files = {}
        self.next_block = 0

    def alloc_block(self, data=b''):
        bid = self.next_block
        self.blocks[bid] = bytearray(data or b'\x00' * self.block_size)
        self.next_block += 1
        return bid

    def write_file(self, name, data):
        blocks = []
        for i in range(0, len(data), self.block_size):
            blocks.append(self.alloc_block(data[i:i+self.block_size]))
        self.files[name] = blocks
        return len(data)

    def read_file(self, name):
        data = bytearray()
        for bid in self.files.get(name, []):
            data.extend(self.blocks[bid][:self.block_size])
        return bytes(data).rstrip(b'\x00')

fs = SimpleFS()
fs.write_file("hello.txt", b"Hello File System!")
print(fs.read_file("hello.txt").decode())

FAQ

What is file system fragmentation?

When a file's data blocks are scattered across the disk instead of contiguous. ext4 uses extents to reduce fragmentation. NTFS resists via B-tree allocation. Defragmentation reorganises data for sequential access.

What is TRIM and why does it matter for SSDs?

TRIM tells the SSD which blocks are free, allowing the controller to garbage-collect them. Without TRIM, SSD write performance degrades. ext4 supports discard, fstrim, and online TRIM.

Can I recover data from a formatted drive?

Formatting typically overwrites file system metadata, not data blocks. Tools like TestDisk and PhotoRec can recover data unless the drive was securely wiped.

Memory Virtualization
Device Drivers
Interprocess Communication

What's Next

You now understand file systems! Next, learn about Device Drivers and kernel module programming, then explore OS Security for protection mechanisms.

  • Practice daily — Run stat /etc/passwd, df -i /, and mount on Linux
  • Build a project — Create a simulated inode-based file system with journaling
  • Explore related topics — Check out ZFS snapshots and Replication for backup

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro