What do the parents and exist_ok arguments to pathlib's Path.mkdir() do?
answer
- Two keywords, two suppressed errors
- One of them means mkdir -p
- Idempotent creation without a pre-check
- A file holding the name still raises
- Check-then-act leaves a race window
basics
~20 sparents=True creates every missing intermediate directory instead of raising FileNotFoundError. exist_ok=True suppresses FileExistsError when the target directory already exists. Both default to False, so a bare mkdir() demands that the parent exists and the target does not.
solid answer
~40 s`Path.mkdir(mode=0o777, parents=False, exist_ok=False)` maps onto one `mkdir` syscall. With the defaults, a missing parent raises `FileNotFoundError` and an existing target raises `FileExistsError`. `parents=True` creates the missing intermediates, like `mkdir -p`; those parents are created with default permissions, ignoring `mode`. `exist_ok=True` swallows `FileExistsError` — but **only** when the thing already there is a directory; if a regular file occupies the name you still get `FileExistsError`. The `os` equivalents are `os.mkdir` and `os.makedirs(..., exist_ok=True)`. Prefer `mkdir(parents=True, exist_ok=True)` over `if not p.exists(): p.mkdir()`: the pre-check leaves a window in which another process or thread creates the directory, and the `exist_ok` form is a single syscall whose error is inspected in C. `mode` is masked by the process umask, so pass it and verify rather than assuming.
code
python · 19 linesfrom pathlib import Path
import tempfile, os
os.chdir(tempfile.mkdtemp())
try:
Path("exports/2026-03-01").mkdir()
except FileNotFoundError as exc:
print("missing parent:", exc.strerror)
Path("exports/2026-03-01").mkdir(parents=True, exist_ok=True)
Path("exports/2026-03-01").mkdir(parents=True, exist_ok=True)
print("idempotent so far")
Path("exports/manifest").write_text("{}", encoding="utf-8")
try:
Path("exports/manifest").mkdir(exist_ok=True)
except FileExistsError as exc:
print("a file holds the name:", exc.strerror)go deeper
Recall the one-liner for 'make this directory if it is not already there': mkdir(parents=True, exist_ok=True). Know which of the two defaults causes FileNotFoundError and which causes FileExistsError.
Explain the mechanics: one syscall per level, parents=True as mkdir -p with default permissions on the intermediates, exist_ok=True suppressing FileExistsError only for an existing directory, and umask masking mode.
Show why the EAFP form is preferred: the exists() pre-check is a check-then-act race that fires as soon as several workers prepare the same directory, and parents=True removes the error that would otherwise catch a wrongly computed destination.
Own where output directories get created at all: one place that derives and validates the destination, restrictive modes where data is sensitive, and idempotent creation so a re-run or a parallel worker is never a special case.
`Path.mkdir()` is `pathlib`'s wrapper over the `mkdir` syscall, and its two keyword arguments each suppress one specific failure of that syscall. ### The default behaviour With no keywords, `Path("exports/2026-03-01").mkdir()` fails in two distinct ways: - If `exports` does not exist, you get `FileNotFoundError` — the syscall creates exactly one directory level and will not invent a parent. - If `exports/2026-03-01` already exists, you get `FileExistsError`. Both are subclasses of `OSError`, and both are ordinary, expected outcomes rather than bugs. ### parents=True This makes the call behave like `mkdir -p`: every missing intermediate directory is created in turn. One detail is easy to miss and is documented — the parents are created **with default permissions, without taking `mode` into account**. So `mkdir(mode=0o700, parents=True)` gives the leaf the restrictive mode you asked for and leaves the intermediates at the default. If the whole chain must be restricted, create the levels yourself, or fix the modes afterwards with `Path.chmod()`. Note also that `parents=True` does not imply `exist_ok=True`: with `parents=True, exist_ok=False`, missing intermediates are fine but an existing *leaf* still raises. ### exist_ok=True This suppresses `FileExistsError` — and here the exact rule matters. It is suppressed only when the final component is an existing **directory**. If a regular file, or a symlink to one, occupies that name, you still get `FileExistsError`. That is the right behaviour: silently continuing when the name is taken by a file would let the next `write_text` into that directory fail in a much more confusing place. ### Why exist_ok beats an exists() pre-check The idiom people reach for first is: ```python if not p.exists(): p.mkdir() ``` This is check-then-act. Between the `exists()` call and the `mkdir()` call, another thread, another worker process or another machine sharing the volume can create the directory, and the `mkdir()` raises the very error the check was meant to avoid. It is not a theoretical race: it is exactly what happens the first time a job runs with a pool of workers all preparing the same output directory. `exist_ok=True` is the EAFP form — one syscall, and if it returns `EEXIST` the error is inspected and dropped inside CPython, with no window in between. The same reasoning gives `Path.touch(exist_ok=True)` for files, and it is the general Python preference for trying the operation over interrogating the world first. The pre-check also costs an extra `stat` on a path you are about to modify anyway, which matters when the call sits inside a loop. ### mode and umask `mode` defaults to `0o777` and is masked by the process umask, so with the usual `0o022` you get `0o755` on disk. Passing `mode=0o700` for a directory that must stay private is right, but verify it rather than assume: the umask can only clear bits, never set them, so an unusual umask can leave you with less than you asked for on the intermediates. ### The os equivalents `os.mkdir(path)` is the single-level call and `os.makedirs(path, exist_ok=True)` is the recursive one, so `Path.mkdir(parents=True, exist_ok=True)` corresponds to `os.makedirs(path, exist_ok=True)`. There is no behavioural difference worth choosing between them; `pathlib` simply keeps you in path objects. ### A concrete shape A flight-schedule differ writes one output directory per comparison day, derived from the input file's location. The bug that shows up in review is an off-by-one boundary in the derivation — `path.parents[1]` where `path.parent` was meant, because `parents[0]` is already the parent, not the file itself — and it is `parents=True` that turns a wrong-by-one-level path into a silently created directory tree in the wrong place rather than a loud `FileNotFoundError`. That is the real tradeoff of `parents=True`: it is convenient, and it removes the error that would otherwise catch a mistake in how the path was computed. Compute the destination deliberately, and consider resolving it and checking it against your intended root before creating anything.
- With exist_ok=True, can Path.mkdir() still raise FileExistsError?Yes. The suppression applies only when the existing entry is a directory. If a regular file — or a symlink to one — already occupies that name, `FileExistsError` is still raised, and that is deliberate: continuing silently would push the failure to whatever later tries to write inside the supposed directory, in a far more confusing place.
- Why prefer mkdir(exist_ok=True) over checking exists() first?Because the pre-check is check-then-act. Between `exists()` returning False and `mkdir()` running, another thread, worker process or machine on the same volume can create the directory, and the call raises the exact error the check was meant to prevent — which is what happens the first time a worker pool prepares a shared output directory. `exist_ok=True` is one syscall whose `EEXIST` result is handled inside CPython, with no window, and it saves a stat as well.
- Does mode apply to the directories created by parents=True?No. Intermediate directories are created with default permissions and the `mode` argument is ignored for them; only the final component gets the mode you passed — and even there it is masked by the process umask, so `0o777` typically lands as `0o755`. If a whole chain must be restricted, create the levels explicitly or fix them afterwards with `Path.chmod()`.
saying these in an interview costs you the question
- Thinks exist_ok=True also creates missing parents
- Guards mkdir with an exists() check instead
- Assumes exist_ok=True suppresses every FileExistsError
- Expects mode to apply to created parent directories
- Believes mkdir creates the whole chain by default
- Treats FileExistsError as a bug rather than a normal outcome