How to list all files in a directory in Python
os.listdir("path") returns every name in a folder — files and subfolders, in no particular order. For the files only, use pathlib: [p for p in Path("path").iterdir() if p.is_file()].
Python gives you four ways to read a directory, and picking the wrong one is where the bugs come from: os.listdir() hands back bare names including the subfolders, pathlib hands backPath objects you can test and open, glob filters by pattern, and os.walk() descends into every subfolder. None of them sort — the order is whatever the filesystem gives, so wrap anything you print in sorted(). Each snippet below builds a small project/ folder first so it runs anywhere; hit Run, then edit it and run it again.
1os.listdir() — every name in the folder
os.listdir() is the shortest answer and the one most people reach for. It returns a plain list of names — not paths — for everything directly inside the folder, subdirectories included:
Output
You get 4 entries and ['README.md', 'logs', 'notes.txt', 'src'] — note that logs and src are directories, and that five files exist but only four entries came back, because listdir never descends. Two things to remember: the names aren't sorted (this listing prints in filesystem order without the sorted()), and they aren't usable paths — you need os.path.join("project", name) before you can open one.
2Path.iterdir() — files onlyRecommended
Path.iterdir() yields Path objects instead of strings, so filtering out the subfolders is one .is_file() call and the result is ready to open. This is the modern idiom, and the direct answer to "files, not folders":
Output
Two rows come back — README.md | project/README.md | .md and notes.txt | project/notes.txt | .txt — then the os.path version prints ['README.md', 'notes.txt']. Same result, but each Path already carries its folder, so p.read_text() or p.stat() works right away, and .name, .stem and .suffix save you the string surgery. Swap is_file() for is_dir() to list the subfolders instead.
3glob — only the files you want
When you only want the .py files or the logs, filter while you list rather than afterwards. Path.glob() takes a shell-style pattern; the older glob.glob() does the same and returns strings:
Output
The three lines print ['project/src/app.py', 'project/src/utils.py'], ['project/README.md'] and ['project/logs/run.log', 'project/src/app.py', 'project/src/utils.py']. * matches within one level, ? matches a single character and [0-9] a character range — so */* reaches exactly one folder deep. One gotcha: glob skips dotfiles, so * will never return .env.
4Listing subfolders too
To reach every file at any depth you need recursion. Path.rglob("*") is the one-liner; os.walk() is the workhorse when you want the folders and files handed to you separately, or want to prune a branch as you go:
Output
rglob("*") returns all seven entries — five files plus the project/logs and project/src directories — so add if p.is_file() when you want files only, or use rglob("*.py") to filter by extension. os.walk() prints the same five files grouped by folder. That dirs.sort() isn't decoration: mutating dirs in place is how you control walk, and dirs[:] = [d for d in dirs if d != ".git"] is how you skip a subtree entirely.
5os.scandir() — names with sizes and types
os.listdir() gives you names, and every is_file() or stat() after it costs another system call. os.scandir() collects that metadata during the listing itself, which is why it's the fast choice on big folders — and it makes "sort by size" or "sort by date" easy:
Output
The table reads README.md 7 bytes, logs <dir>, notes.txt 5 bytes, src <dir>, then biggest: project/src/utils.py 28 bytes. Use scandir in a with block so the directory handle closes, and consume it as you go — it's an iterator, not a list. For newest-first instead of biggest-first, sort on p.stat().st_mtime.
6Which should you use?
| Method | Returns | Best for |
|---|---|---|
| Path(d).iterdir() | Path objects | The default — filter with .is_file() |
| os.listdir(d) | List of names | A quick look; you join the paths yourself |
| Path(d).glob("*.py") | Matching paths | One extension or name pattern |
| Path(d).rglob("*") | Every depth | Recursive listing in one line |
| os.walk(d) | (root, dirs, files) | Recursion where you skip subtrees |
| os.scandir(d) | Entries + metadata | Big folders, or sorting by size/date |
Frequently asked questions
How do I list only the files and not the folders?
Filter as you list: [p for p in Path("data").iterdir() if p.is_file()]. With the os module it is [n for n in os.listdir("data") if os.path.isfile(os.path.join("data", n))] — the join matters, because os.listdir returns bare names and isfile("app.py") would be checked against your current working directory instead. Swap in is_dir() to get the subfolders.
Does os.listdir() return files in alphabetical order?
No. The order is whatever the filesystem reports — it often looks sorted on macOS and arbitrary on Linux, and it can change as files are added or deleted. Never rely on it: call sorted(os.listdir(path)), or sorted(Path(path).iterdir()) for paths. The same applies to iterdir(), glob() and os.walk().
How do I list all files in subdirectories too?
[p for p in Path("project").rglob("*") if p.is_file()] walks every level in one line, and rglob("*.py") narrows it to one extension. When you need to skip a branch — node_modules, .git — use os.walk() instead and prune in place with dirs[:] = [d for d in dirs if d != ".git"], which stops it descending at all.
How do I list hidden files, or exclude them?
os.listdir() and iterdir() include dotfiles already; glob("*") deliberately excludes them, so add glob(".*") to catch them. To exclude them, filter on the name: [p for p in Path(d).iterdir() if not p.name.startswith(".")]. On Windows the hidden flag is an attribute rather than a leading dot, readable via p.stat().st_file_attributes.
How do I sort files by date modified or by size?
Sort on the stat result: sorted(files, key=lambda p: p.stat().st_mtime, reverse=True) puts the newest first, and st_size sorts by size. Use datetime.fromtimestamp(p.stat().st_mtime) to print the timestamp. On a large folder, list with os.scandir() — its entries cache the metadata, so sorting does not trigger a fresh stat per file.
What is the difference between os.listdir, os.scandir and glob?
os.listdir() returns names only, so every follow-up question (file or folder? how big?) costs another system call. os.scandir() returns entries that already carry that metadata, which makes it markedly faster on large directories — os.walk() is built on it. glob adds pattern matching and recursion (**) on top, at the cost of doing more work per call.
Why do I get FileNotFoundError or NotADirectoryError?
The path does not exist, or it points at a file rather than a folder — usually a relative path resolved against the wrong working directory. Print Path.cwd() to see where you actually are, and prefer absolute paths built from Path(__file__).parent. To check first, use Path(d).is_dir(); Path(d).glob("*") on a missing directory yields nothing instead of raising.