Directories are files about files
A directory, or folder, is not a container that physically holds files. It is essentially a small table mapping names to files.
| Name | Points at |
|---|---|
a.txt | file record 4821 |
b.txt | file record 4822 |
The filesystem tracks each file's content and metadata separately from its name, and that design explains everyday behavior.
Moving a file within a disk is instant, even for a 50 GB file, because only the name entry moves and zero content bytes are copied.
A path such as /Users/ada/mydata/a.txt is just directions: start at the root directory /, look up Users, then ada, then mydata, then a.txt. Each step is one table lookup, which is why a deeply nested path costs slightly more to resolve than a shallow one.
Python's os module can create directories, list their name tables, and read any file's metadata with os.stat().
Listing a name table and reading a size
A directory, two tiny files, and one metadata lookup.
import os os.makedirs("mydata", exist_ok=True) for name in ["a.txt", "b.txt"]: with open(os.path.join("mydata", name), "w") as f: f.write("x") print(sorted(os.listdir("mydata"))) print("a.txt size:", os.stat("mydata/a.txt").st_size)
Output
['a.txt', 'b.txt'] a.txt size: 1
os.listdir reads the directory's name table, and os.stat returns the file's metadata record, where st_size is the byte count. It is 1 because "x" is one byte.
Two details make this code portable. exist_ok=True means rerunning the script is not an error, and os.path.join builds the path with the right separator for the platform rather than hardcoding a slash.
Append mode next to write mode
One file written, then extended, then measured.
import os with open("appendix.txt", "w") as f: f.write("line 1\n") with open("appendix.txt", "a") as f: f.write("line 2\n") with open("appendix.txt", "r") as f: print(f.read(), end="") print("size:", os.stat("appendix.txt").st_size)
Output
line 1 line 2 size: 14
Mode "w" truncates the file, wiping whatever was there, while mode "a" appends to the end. Each line is 7 bytes, 6 characters plus the newline, so the size is 14.
| Mode | On an existing file |
|---|---|
"w" | truncates to empty, then writes |
"a" | keeps the content, writes at the end |
"r" | reads, cannot write |
Reaching for "w" when "a" was meant is a genuinely destructive mistake, because the truncation happens the instant the file is opened, before a single byte is written.
Why a same-disk move is instant
Moving a 50 GB file to another folder on the same disk takes under a second because only a directory's name table changes, while moving it to a USB drive must copy every content byte to the other device.
Within one filesystem a move is a metadata edit: remove the name from one directory table and add it to another. Across devices there is no shared table, so the OS must copy all 50 GB of content bytes and then delete the original.
| Move | Work performed | Time |
|---|---|---|
| same filesystem | two table edits | milliseconds |
| different device | copy plus delete | proportional to size |
This also explains a failure mode people find confusing. A cross-device move that runs out of space on the destination can leave the original intact and the copy partial, because the delete only happens after the copy succeeds.
Which ingredient st_size comes from
os.stat("a.txt").st_size reporting 1 for a file containing "x" is reading metadata, not the content bytes.
The filesystem keeps a record per file holding name links, size, owner, permissions, and timestamps, separately from the content. os.stat reads that record, which is why checking the size of a 50 GB file is instant.
| Question | Answered from |
|---|---|
| how big is this file | metadata |
| who owns it | metadata |
| what does it say | content |
The filesystem answers without opening or reading the file's bytes at all. That separation is the same one that made the instant move possible in the previous block, and it is the core design idea of a filesystem.