Dependency Resolution

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

Every pip install has to pick one version of every package such that all requirements hold at the same time. Since pip 20.3 a backtracking resolver does this. This page explains how it works, what its messages mean and what to do when it fails, for pip 26.2.2. Every example ran on CPython 3.14.6 on 2026-10-22 against PyPI as it was that day; the results depend on the versions published at the time.

How the resolver works

The resolver starts from your requirements and repeatedly:

  1. picks a requirement it has not satisfied yet (the preference heuristics below decide which);

  2. asks the finder for the candidates that match it, newest first, and pins the best one;

  3. reads that candidate’s dependencies (from PEP 658 metadata or the downloaded file) and adds them as new requirements;

  4. if a new requirement contradicts a pin, backtracks: un-pins the most recent decision that caused it and tries the next candidate.

It stops when every requirement is satisfied (success) or when every combination has been ruled out (ResolutionImpossible).

flowchart TD A["Requirements from the command line, files, groups"] --> B{Unsatisfied requirement left?} B -->|No| Z["Install the pinned set"] B -->|Yes| C["Choose the next requirement (get_preference)"] C --> D["Finder: candidates that match, newest first (find_matches)"] D --> E{Any candidate left?} E -->|Yes| F["Pin it and read its dependencies (get_dependencies)"] F --> G{Consistent with every pin so far?} G -->|Yes| B G -->|No| H["Backtrack: drop the pin, try the next candidate"] H --> E E -->|"No, nothing earlier to revisit"| X["ResolutionImpossible"] E -->|"No, but an earlier pin can change"| H

Backtracking in practice

Asking for rich>=10 together with pygments<2.13 forces backtracking: every recent rich requires pygments>=2.13, so pip has to walk back through rich releases until it finds one that accepts an older Pygments:

python -m pip install --dry-run --ignore-installed "rich>=10" "pygments<2.13"
Collecting rich>=10
Collecting pygments<2.13
Collecting markdown-it-py>=2.2.0 (from rich>=10)
INFO: pip is looking at multiple versions of rich to determine which version is compatible with other requirements. This could take a while.
Collecting rich>=10
INFO: pip is still looking at multiple versions of rich to determine which version is compatible with other requirements. This could take a while.
INFO: This is taking longer than usual. You might need to provide the dependency resolver with stricter constraints to reduce runtime. See https://pip.pypa.io/warnings/backtracking for guidance. If you want to abort this run, press Ctrl + C.
Collecting markdown-it-py<3.0.0,>=2.2.0 (from rich>=10)
Collecting rich>=10
Collecting mdurl~=0.1 (from markdown-it-py<3.0.0,>=2.2.0->rich>=10)
Would install Pygments-2.12.0 markdown-it-py-2.2.0 mdurl-0.1.2 rich-13.2.0

(The … metadata (…) download lines are left out.) The run took 39 seconds of wall time, almost all of it spent fetching the metadata of old rich releases, and ended on rich 13.2.0, the newest release that accepts Pygments<2.13. The INFO notices are printed when the resolver has backtracked on the same package several times; they are progress messages, not errors.

Reducing backtracking

The pip documentation lists, in order:

  1. Let it finish. Most backtracking runs succeed, and a failure after trying everything is a real answer.

  2. Narrow the versions of the package pip backtracks on: here "rich>=13,<13.3" would have avoided the walk entirely.

  3. Use constraints files or lock files so that the work is done once, during development, and deployments install known versions; see Constraints & Build Constraints and Lock Files & Reproducible Installs.

ResolutionImpossible

The pip flavour of birdtally-report pins birdtally[pretty]==0.1.0. Someone adds rich<13 to its requirements to work around an unrelated bug:

birdtally[pretty]==0.1.0
rich<13
python -m pip install --dry-run --ignore-installed --find-links dist -r conflict.txt
Looking in links: dist
Processing ./dist/birdtally-0.1.0-py3-none-any.whl (from birdtally[pretty]==0.1.0->-r conflict.txt (line 1))
Collecting rich<13 (from -r conflict.txt (line 2))
INFO: pip is looking at multiple versions of birdtally[pretty] to determine which version is compatible with other requirements. This could take a while.
ERROR: Cannot install birdtally[pretty]==0.1.0 and rich<13 because these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested rich<13
    birdtally[pretty] 0.1.0 depends on rich>=13; extra == "pretty"

Additionally, some packages in these conflicts have no matching distributions available for your environment:
    rich

To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip to attempt to solve the dependency conflict

ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts

Read it from the top:

  • Cannot install birdtally[pretty]==0.1.0 and rich<13 names the user requirements that cannot all hold.

  • The conflict is caused by: lists every requirement on the package in conflict, and who made it. "The user requested" means your command line, requirements file or group; (constraint) marks a constraints file; X depends on … is metadata of package X. The marker extra == "pretty" tells you which extra pulled the requirement in.

  • no matching distributions available for your environment: rich says that, after intersecting <13 and >=13, no rich file is left at all (it also appears when only wheels for other platforms or Pythons exist).

  • To fix this you could try to: is generic advice; the specific fix depends on which requirement is wrong.

The possible solutions, from the documentation: loosen your own pins (here drop rich<13), ask the upstream project to loosen theirs, pick a different version of the package whose requirement conflicts (an older birdtally without the rich>=13 bound, if one existed), or, as a last resort, install with --no-deps and accept an environment that pip check will flag.

ResolutionTooDeep

On very large graphs the resolver can exceed its round limit and stop with ResolutionTooDeep before reaching an answer either way. The documented remedies all shrink the search space:

  • Reasonable lower bounds ("package_coffee>=0.44.0"): old releases are where most of the backtracking happens.

  • --upgrade, so that pip does not explore around versions that happen to be installed.

  • Constraints on transitive dependencies.

  • Upper bounds, sparingly: they help the resolver rule candidates out sooner, but in published metadata they cause the conflicts above for everyone else; keep them in your own constraints or lock files.

Inside the resolver

Why resolving Python packages is hard

The general problem is NP-complete, and Python adds its own difficulties: dependencies are known only after a candidate’s metadata has been fetched (and, for an sdist without trustworthy metadata, after building it), metadata can differ between the files of one version, markers make dependencies depend on the environment, and indexes are slow compared with a local database.

The finder and the resolver

The finder knows about indexes and files: it lists every candidate for a name, applies --only-binary, --uploaded-prior-to, Requires-Python, yanking and the pre-release rules, and orders the result. The resolver knows nothing about files; it works on abstract candidates and requirements. It is the separate resolvelib library, which pip vendors, driven by a provider that implements six operations:

Method Role

identify

The identity of a candidate or requirement (name plus extras).

get_preference

Which unresolved requirement to work on next.

narrow_requirement_selection

Limits the set passed to get_preference.

find_matches

The candidates that satisfy a set of requirements, from the finder.

is_satisfied_by

Whether a candidate satisfies a requirement.

get_dependencies

The dependencies of a candidate, from its metadata.

The heuristics are in the two selection methods. pip 26.2.2 narrows the choice to Requires-Python first, then to the causes of the current conflict, then to identifiers that have appeared unresolved in conflicts at least five times. Among those it prefers, in order: requirements involved in repeated conflicts, direct URL requirements, exact pins (== without a wildcard, ===), requirements with an upper bound, user requirements in the order given, any requirement with an operator, and finally alphabetical order.

Lazy metadata. get_dependencies is the expensive step. With PEP 658 metadata files (served by PyPI for every wheel), pip downloads only the METADATA of a candidate, not the whole wheel; that is what the … .whl.metadata (18 kB) lines in the output are. The --use-feature=fast-deps flag tries the same trick with HTTP range requests on indexes that lack PEP 658.

The 20.3 change and the legacy resolver

Before pip 20.3 (2020-11-30) pip could install a combination of packages that violated declared requirements: in pip 20.0, pip install "six<1.12" "virtualenv==20.0.2" "successfully" installed six==1.11 although that virtualenv requires six>=1.12.0,<2. pip 20.3 made the backtracking resolver the default (20.1 and 20.2 had shipped it behind a flag). The user guide’s "watch out for" list from that change is still the list of things people trip over:

  • it reduces inconsistency: pip no longer installs a mutually inconsistent set, it refuses instead;

  • it is stricter: two packages with incompatible requirements fail rather than install broken;

  • it considers only the packages in the current command, so pip install x followed by pip install y can still break x (see the consistency pitfall);

  • constraints were stripped down to version limits: they do not override requirements, unnamed requirements, links and extras are not allowed in them, and pip may upgrade or downgrade packages to satisfy them;

  • an editable requirement (-e .) no longer makes pip ignore version specifiers or constraints;

  • hash-checking mode needs == pins and may not combine well with constraints.

--use-deprecated=legacy-resolver still exists in pip 26.2.2, but the old resolver is "deprecated and unsupported", newer options such as --only-deps refuse to work with it, and it will be removed. Fix the conflict instead.

Poetry’s solver

Poetry resolves differently. Its solver (derived from the PubGrub algorithm) resolves a whole project at once, for every platform and every Python allowed by requires-python, and writes the result to poetry.lock; poetry install then does not resolve at all. A Poetry conflict therefore appears at poetry lock or poetry add time, worded as a chain of Because … depends on …, version solving failed. statements, and a mismatch between the project’s Python range and a dependency’s is a common cause that pip would not report. See Poetry: Troubleshooting & FAQ.

Getting help

When asking for help with a resolution problem, include the full output of the failing command run with -vvv (or --log), the exact requirements and constraints, and python -m pip --version; the pip documentation asks for exactly that.