Why does a pip install sometimes spend minutes backtracking, and how do you cut that short?
answer
- It is searching, not hanging
- Newest first, then back up
- Every candidate costs metadata
- Source distributions must be built to answer
- Bound the range it keeps re-downloading
basics
~20 spip's resolver tries the newest candidate for each requirement and, on a conflict, backs up to older ones. Every candidate costs a metadata fetch, and a source distribution costs a build, so a wide unbounded search runs for minutes. Narrow the ranges.
solid answer
~50 sSince pip 20.3 the default resolver is a backtracking one: it picks candidate versions newest-first, and when a combination turns out to be unsatisfiable it undoes a choice and tries an older version. To know what a candidate requires it needs that candidate's metadata — cheap for a wheel whose index serves metadata separately, expensive for a source distribution, which must be downloaded and have its metadata built. Wide or unbounded ranges over a long release history make the search space enormous, and pip itself prints that it is taking longer than usual and that stricter constraints would help. The fix is to look at which distribution the log keeps downloading in descending version order and give the resolver a bound on it — an upper limit or the exact version you need — or to remove the requirement that cannot be satisfied at all.
code
console · 1 linepython -m pip install --dry-run "pip<26"go deeper
Recall that a long install is usually the resolver searching, not a hang, and that the log showing the same distribution downloaded at descending versions is the tell. Know that pinning that one requirement usually ends it.
Explain the mechanics: newest-first candidate selection with backtracking on conflict, metadata cost per candidate, why source distributions are far more expensive than wheels, and why an unbounded range makes the search space huge.
Demonstrate diagnosis under time pressure: reading the log to find the churn point, deciding whether the search is solvable at all, bounding requirements deliberately, and stopping repeat searches in CI by reusing a resolved answer instead of re-resolving every build.
Own the policy: where the org sets upper bounds, how much resolution time is acceptable in a build, whether builds resolve at all or install a pre-resolved set, and the tradeoff between tight bounds that stall upgrades and loose ones that slow every build.
## What backtracking actually is pip's resolver has to produce a set of versions that satisfies every requirement at once. It does that by choosing candidates one at a time, newest first, and checking the requirements each choice drags in. When a choice leads to a contradiction, it **backtracks**: it undoes the most recent choice, takes the next-oldest candidate, and continues. That is a search, and its cost is the number of candidate combinations it has to examine before it either finds a consistent set or proves there is none. This became the default in **pip 20.3** (2020). The old resolver simply took the first requirement it saw and ignored later conflicting ones, which was fast and frequently produced a broken environment. The trade is honest: correctness for time. The legacy behaviour was later removed from pip entirely, so “turn the old one back on” is not an available escape hatch. ## Why each step is expensive To evaluate a candidate, pip must know that candidate's requirements. For a **wheel**, the metadata is a static file inside the archive, and since pip 22.3 pip can fetch just that metadata file from an index that serves it (PEP 658) rather than downloading the whole wheel — which is why backtracking over wheel-only projects is comparatively quick. For a **source distribution**, there is no guaranteed static answer: pip must download the sdist and ask its build backend to prepare metadata, which can mean creating an isolated build environment and installing build dependencies. Do that fifty times while walking backwards through a release history and you have your several minutes. Two things blow the search up: * **No upper bound anywhere.** A requirement like `shared-lib` with no cap gives the resolver the entire release history as candidates. * **An unsatisfiable requirement.** If nothing can satisfy the set, pip does not know that until it has exhausted the search, so the pathological case is a long grind that ends in a conflict report anyway. A third, easy to miss: **`Requires-Python`**. Candidates whose declared interpreter range excludes the interpreter you are installing under are skipped. Shortly after a new interpreter release, a project that has not yet published a compatible wheel or widened its range can send the resolver deep into its own history looking for something installable — a slow install that is really a compatibility problem wearing a disguise. ## Reading the log The signal is repetition. When the output shows the same distribution being downloaded over and over at descending versions — ```text Downloading shared_lib-3.2.0-py3-none-any.whl Downloading shared_lib-3.1.0-py3-none-any.whl Downloading shared_lib-3.0.1-py3-none-any.whl ``` — that distribution is the one the resolver is churning on, and pip will eventually print its own note that the install is taking longer than usual and that stricter constraints would reduce runtime. Verbose output shows the choices in more detail if you need to see which requirement is pushing it backwards. ## Making it fast * **Bound the churning requirement.** Pin it to the version you actually intend to run, or give it a realistic upper limit. This is the single highest-leverage change: it removes most of the candidate space. * **Fix the impossible requirement instead of waiting.** If the search is doomed, the minutes buy nothing. Reading the eventual conflict report and removing the contradiction is the real fix. * **Prefer environments where wheels exist** for your interpreter and platform. A dependency with no wheel is the expensive one, both to resolve and to build. * **Resolve once, reuse the answer.** Repeated installs of the same requirement set should not re-run the search; that is what a resolved, fully-pinned artefact is for. * **Inspect before committing.** A dry run shows you the plan the resolver arrived at without modifying the environment, which is useful when you are experimenting with bounds. * **Keep pip current.** Metadata-only fetching and resolver improvements arrive in pip releases, and an old pip does strictly more work. The judgement to carry: a slow install is a *symptom*, and the two underlying causes are very different. Either your ranges are too loose and the resolver is doing real work you can hand it an answer to, or your requirements are contradictory and no amount of waiting will help. Deciding which one you are looking at, early, is what separates a two-minute fix from an afternoon.
- How do you tell a slow-but-solvable resolution from one that is doomed to fail?Look at what is being retried and why. If the resolver is walking one distribution backwards because your ranges are loose, bounding that requirement ends it in seconds. If two requirements have no version in common, the search can only end in a conflict report, so the faster path is to reproduce the pair in isolation — install the two requesters alone — and read the report immediately rather than waiting out the full search.
- Why can an install be slow on a fresh interpreter release but fast on the previous one?Candidates whose declared `Requires-Python` range excludes your interpreter are not installable, so the resolver skips them and keeps going back through the history looking for one that is. On a recently released interpreter, projects that have not yet published compatible wheels force exactly that walk, and the sdists it finds along the way each need a metadata build.
- Why does bounding one requirement often speed up resolution of an unrelated one?Because the search is combinatorial. Fixing one distribution's version also fixes the requirements it contributes, which prunes whole branches the resolver would otherwise have explored for its siblings. That is why pip's own advice is to give the resolver stricter constraints rather than to keep everything maximally open.
saying these in an interview costs you the question
- Says the install has hung and kills it
- Blames network speed for every long resolution
- Thinks clearing the cache fixes backtracking
- Believes reverting to the legacy resolver is still an option
- Adds looser ranges to help the resolver find something
- Never notices the repeated descending-version downloads in the log