pip and pdm use the same resolver
September 1, 2026 · 2 min read
Something I assumed for a long time without checking: pdm (and Poetry, and every lockfile tool) must ship a smarter dependency resolver than pip. Not true. pip's resolver has been built on resolvelib since 20.3, and pdm's resolver is built on the same library. So is ansible-galaxy's, for what it's worth.
resolvelib knows nothing about Python packaging. It's a small backtracking loop: take requirements, ask a "provider" for matching candidates, pick one, propagate its dependencies, backtrack on conflict. What a candidate is, where metadata comes from, which of two versions is preferable — all of that lives in the provider each tool writes. Comparing pip and pdm on resolver quality is mostly comparing the same engine with itself. What differs is the question.
pip resolves for the machine it's running on. Markers like sys_platform == "win32" are evaluated against the current environment during resolution, and whatever doesn't apply gets dropped on the spot. The answer is valid here and now, and nowhere else.
pdm resolves for the whole requires-python range declared in pyproject.toml, on any platform. Markers survive into the lockfile as data instead of being evaluated away. One lock covers the Linux CI runner, the mac laptop and a 3.10-to-3.13 support matrix, and installing from it involves no resolution at all: evaluate markers locally, download, check hashes. This is also why locking feels slower than installing — the resolver can't discard candidates that don't match the current machine, it has to keep the entire matrix consistent at once. When that gets painful (or genuinely unsatisfiable), pdm's lock targets let you narrow the lock to explicit platform/Python pairs.
The other difference is what the output is. pip's output is a mutated environment, and nothing records how it got there; run the same install two weeks later and a transitive dependency may have moved under you. pdm's output is a file, pins plus hashes, committed next to the code. In practice that means conflicts surface at pdm lock on a developer machine rather than during a deploy, and adding one dependency re-resolves against the existing pins instead of letting the whole graph drift.
Historically every tool invented its own lock format (pdm.lock, poetry.lock, uv.lock, pip-compile's pinned requirements.txt). PEP 751, accepted in 2025, standardizes pylock.toml, so the tool that writes a lock and the tool that installs from it can finally be different programs.
Footnote: pdm can nowadays delegate resolution and installation to uv (pdm config use_uv true). That one really does swap the engine — uv's resolver is its own code, in Rust, and much faster. The contract doesn't change though. The deliverable is still a cross-environment lockfile, and the question being asked is still the wide one. Docs on how pip's side works are at pip's dependency resolution page; the marker semantics are PEP 508.