ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
Topics
sre111localhost lab102performance70python58javascript47bundle size36benchmarking35node.js35web development21typescript19benchmark16content creation15All topics
About
Loading article…

ShopperCove

Hands-on notes on developer tools, written after the tool has actually been run.

All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — flat names (p50 entries/s)
  5. When you need `is_file` (n=2000)
  6. Nested walk (2050 entries)
  7. Reading it
  8. Names-only vs metadata
  9. pathlib tax
  10. Pitfalls
  11. Reproduce
  12. Limits
  13. Takeaway
  14. FAQ
  15. Is os.scandir faster than os.listdir?
  16. Why is scandir faster with is\_file?
  17. What about pathlib Path.iterdir?
  1. Blog
  2. /Observability & SRE

scandir vs listdir in Python: Which Is Faster? Tested

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.

30 September 2026·4 min read

Lab

Disclosure

This post contains affiliate links. If you buy through them, I may earn a commission at no extra cost to you. I only make firsthand claims about products I have actually used.

On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — flat names (p50 entries/s)
  5. When you need `is_file` (n=2000)
  6. Nested walk (2050 entries)
  7. Reading it
  8. Names-only vs metadata
  9. pathlib tax
  10. Pitfalls
  11. Reproduce
  12. Limits
  13. Takeaway
  14. FAQ
  15. Is os.scandir faster than os.listdir?
  16. Why is scandir faster with is\_file?
  17. What about pathlib Path.iterdir?

Intro — what this post promises

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:

  • 01shoppercove.comglob vs rglob vs walk localhost lab
  • 02shoppercove.commmap vs read scan localhost lab
  • 03shoppercove.combytesio vs spooled tempfile localhost lab
  • 04shoppercove.comshutil copyfile vs manual localhost lab
  • 05shoppercove.compathlib vs ospath localhost lab
  • 06shoppercove.comtarfile vs zipfile localhost lab
  • 07shoppercove.comsecrets vs urandom localhost lab
  • 08shoppercove.comtextwrap fill vs manual localhost lab

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.


Arms

ArmPattern
os.listdirnames only
listdir + joinbuild full paths
os.scandiriterate DirEntry
Path.iterdirpathlib objects
listdir + isfilestat each path
DirEntry.is_file()cached type from scandir
nestedos.walk vs scandir stack vs rglob("*")

Lab topology

flat: 500 / 2000 / 8000 files · 7 rounds · p50
isfile check at n=2000
nested: 50 dirs × 40 files (2050 entries)
metric: entries/s

Script: lab-evidence/90-scandir-vs-listdir/results/run_lab.py.


Lead table — flat names (p50 entries/s)

nlistdirlistdir+joinscandirPath.iterdir
5004187.5k1752.6k3563.5k928.0k
20004380.1k2136.0k3374.3k1046.3k
80004288.1k2119.9k3279.9k1079.8k

When you need is_file (n=2000)

Armentries/s
scandir DirEntry.is_file()3056.2k
listdir + os.path.isfile353.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.


Nested walk (2050 entries)

Armentries/s
scandir stack walk2434.0k
os.walk2112.6k
Path.rglob("*")547.2k

Reading it

  • Names only: listdir is fine (and slightly fastest here).
  • Need type/stat: prefer scandir / DirEntry.
  • Path.iterdir is ergonomic; expect a measurable object-creation tax on huge directories.
  • Use os.walk or a scandir stack for trees; rglob("*") was ~4.45× slower on this nest.

Names-only vs metadata

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.


pathlib tax

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.


Pitfalls

  • Calling os.path.isfile on every listdir name in a hot path.
  • Assuming scandir is always fastest for names-only (not on this box).
  • Forgetting with os.scandir(...) (resource lifetime).
  • Following symlinks unintentionally in recursive walks.

Reproduce

python3 lab-evidence/90-scandir-vs-listdir/results/run_lab.py

Evidence: summary.json, fixture_tree/.


Limits

One Linux box, synthetic files on local FS. Not networked filesystems / huge directories with millions of entries. Cold-cache dentries may differ.


Takeaway

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.

FAQ

Is os.scandir faster than os.listdir?

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).

Why is scandir faster with is_file?

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.

What about pathlib Path.iterdir?

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.

​

os.scandiros.listdirpath.iterdirdirentrydirectory listinglocalhost labsrepython

Lab evidence

What I found running this

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.

Ready Client Sites for prebuilt websites

See Ready Client Sites details

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Explore the product

Untested by us. Check what's included, the license and refund terms before you buy.

Ready Client Sites for prebuilt websites

See Ready Client Sites details

Keep reading

  • fnmatch vs re Name Filter: Localhost Lab

    Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.

    Lab1 Oct 20264 min read

  • tarfile vs zipfile Create+Extract: Localhost Lab

    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

  • rglob vs glob: Which Is Faster in Python?

    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