Hands-on os.scandir vs listdir vs Path.iterdir lab: real entries/s for names and is_file on a synthetic tree, measured on Linux localhost (lab) for SREs.
4 min read
For a plain list of names, os.listdir is slightly faster than os.scandir (about 4,288k vs 3,280k entries/s on an 8,000-file directory in Python 3.13.5), but once you check file type os.scandir wins by about 8.6x (3,056k vs 354k entries/s at 2,000 entries) because DirEntry.is_file() reuses type data from the directory read. Use listdir for simple name dumps and scandir whenever you branch on is_file, is_dir or stat.
It is not a remake of the glob vs rglob vs walk localhost lab (pattern matching / recursive globs). Here the question is directory iterator APIs and when cached DirEntry metadata wins.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Fixtures under lab-evidence/90-scandir-vs-listdir/results/fixture_tree/. Affiliates: 0. No Docker.
Verdict up front (2000 entries, is_file path): scandir ~3056.2 k/s vs listdir+isfile ~353.7 k/s (~8.64×) vs Path.iterdir().is_file ~242.4 k/s. For names only at 8000 files, listdir ~4288.1 k/s edged scandir ~3279.9 k/s; iterdir ~1079.8 k/s.
| Arm | Pattern |
|---|---|
os.listdir | names only |
listdir + join | build full paths |
os.scandir | iterate DirEntry |
Path.iterdir | pathlib objects |
listdir + isfile | stat each path |
DirEntry.is_file() | cached type from scandir |
| nested | os.walk vs scandir stack vs rglob("*") |
Script: lab-evidence/90-scandir-vs-listdir/results/run_lab.py.
| n | listdir | listdir+join | scandir | Path.iterdir |
|---|---|---|---|---|
| 500 | 4187.5k | 1752.6k | 3563.5k | 928.0k |
| 2000 | 4380.1k | 2136.0k | 3374.3k | 1046.3k |
| 8000 | 4288.1k | 2119.9k | 3279.9k | 1079.8k |
is_file (n=2000)| Arm | entries/s |
|---|---|
scandir DirEntry.is_file() | 3056.2k |
listdir + os.path.isfile | 353.7k |
Path.iterdir + is_file() | 242.4k |
~8.64× — this is the scandir headline. Extra stat per name is the tax DirEntry avoids when the type is already known from the directory read.
| Arm | entries/s |
|---|---|
| scandir stack walk | 2434.0k |
os.walk | 2112.6k |
Path.rglob("*") | 547.2k |
listdir is fine (and slightly fastest here).scandir / DirEntry.Path.iterdir is ergonomic; expect a measurable object-creation tax on huge directories.os.walk or a scandir stack for trees; rglob("*") was ~4.45× slower on this nest.If you only print names, listdir avoids building DirEntry objects and won slightly here. The moment you call isfile/isdir/stat on each name, you pay syscalls that scandir often already satisfied. Rule of thumb from this box: branching on type → scandir; names dump → listdir is acceptable.
Path.iterdir returns Path objects — great for chaining .suffix / .with_name. On an 8000-file directory that object churn showed up as ~4.0× slower than raw listdir. Use pathlib at the edges; tight loops may want scandir/listdir.
os.path.isfile on every listdir name in a hot path.with os.scandir(...) (resource lifetime).Evidence: summary.json, fixture_tree/.
One Linux box, synthetic files on local FS. Not networked filesystems / huge directories with millions of entries. Cold-cache dentries may differ.
For name listing, listdir ~4288.1 k/s ≈ scandir ~3279.9 k/s at 8000 files. For is_file filtering, scandir ~8.64× beat listdir+stat. Default to scandir when you branch on file type; keep listdir for simple name dumps.
Only when you need file type or stat. For names only, listdir edged scandir at every size tested (500, 2,000 and 8,000 files).
listdir returns bare names, so os.path.isfile has to stat each path. scandir's DirEntry already knows the type from the directory read, which avoids that extra syscall.
It was the slowest: about 1,080k names/s at 8,000 files (around 4x slower than listdir) and 242k entries/s with is_file. Use it where Path methods matter more than speed.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. Flat 8000 names: listdir 4288.1k/s; scandir 3279.9k/s; iterdir 1079.8k/s. is_file n=2000: scandir 3056.2k/s vs listdir+isfile 353.7k/s (~8.64x). Affiliates: 0. Evidence: lab-evidence/90-scandir-vs-listdir/.
Explore the product
Untested by us. Check what's included, the license and refund terms before you buy.
Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.
Lab1 Oct 20264 min read
Hands-on tarfile vs zipfile create+extract lab: real MB/s on a mixed small-file fixture (uncompressed tar vs zip), measured on Linux localhost for SREs.
Lab1 Oct 20264 min read
Hands-on glob.glob vs Path.rglob vs os.walk lab: real files/s for recursive file listing on a modest fixture tree, measured on Linux localhost for SREs.
Lab1 Oct 20264 min read