Python Pip Dependency Resolution and Conflicts

Python’s package installer, pip, manages complex environments by recursively parsing package requirements and automatically reconciling version mismatches. Since the release of version 20.3, pip has used a backtracking resolver powered by the resolvelib library, moving away from its legacy greedy approach. This article examines how pip discovers recursive dependencies, traverses the dependency tree, resolves conflicting constraints via backtracking, and handles unresolvable dependency conflicts.

The Recursive Discovery Process

When a package is installed using pip install, the resolver evaluates the requested package's metadata (found in distribution wheels or source packages via pyproject.toml, setup.cfg, or setup.py).

  1. Root Inspection: pip parses the primary package's direct dependencies and records their version specifiers.
  2. Recursive Traversal: For each required dependency, pip downloads package metadata from the index (such as PyPI) without downloading the entire package archive if metadata is available. It extracts downstream dependencies, continuing this process iteratively until all branches of the dependency tree terminate.
  3. Graph Construction: The result is a Directed Acyclic Graph (DAG) representing the complete set of required direct and transitive packages along with their specified version ranges.

The Backtracking Resolution Algorithm

Older versions of pip used a naive, "first-seen-wins" approach. If Package A required foo==1.0 and Package B required foo==2.0, pip would install whichever was encountered first, leaving the environment broken.

Modern pip uses a backtracking algorithm that evaluates the dependency tree globally:

Resolving Incompatible Version Conflicts

When constraints are mutually exclusive, no valid set of packages can satisfy the environment. For example:

Because no release of requests can satisfy both conditions simultaneously, pip exhausts all possible backtracking combinations across the dependency tree. Once all candidate combinations fail, pip aborts the installation entirely to avoid corrupting the environment. It outputs a ResolutionImpossible error detailing:

Controlling Resolver Behavior

Users can manage how pip resolves dependencies through specific flags: