# How uv Solves Dependencies So Fast


> [!TIP]
> Want more on uv? Browse every uv tutorial, how-to, reference, and explanation on the [uv topic page](https://pydevtools.com/handbook/topics/uv/).

Python dependency resolution is NP-hard in the formal sense: it reduces to [Boolean satisfiability](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem). In a [Jane Street tech talk](https://www.janestreet.com/tech-talks/uv-an-extremely-fast-python-package-manager/) ([YouTube](https://youtu.be/gSKTfG1GXYQ)), Charlie Marsh walked through why [uv](https://pydevtools.com/handbook/reference/uv.md)'s resolver has to solve this problem and the architectural decisions that let it do so 10-100x faster than [pip](https://pydevtools.com/handbook/reference/pip.md).

## Python can't bail out

Rust and Node have an escape hatch: if two packages need different versions of the same dependency, the runtime can load both. Python cannot. Imports use a global cache keyed by module name, so you cannot have [Pydantic](https://docs.pydantic.dev/) 1 and Pydantic 2 installed at the same time. If [vLLM](https://github.com/vllm-project/vllm) requires Pydantic 2 and an old version of [LangChain](https://github.com/langchain-ai/langchain) requires Pydantic 1, the dependency graph has no solution.

This constraint forces the resolver to search a potentially enormous space of version combinations to find one consistent set. [Cargo's resolver](https://github.com/rust-lang/cargo) does a graph traversal and uses multi-version as an escape valve when things get hard. uv's resolver has no such valve. It uses a [SAT solver](https://en.wikipedia.org/wiki/SAT_solver) based on [CDCL (conflict-driven clause learning)](https://en.wikipedia.org/wiki/Conflict-driven_clause_learning), a technique from formal verification that prunes the search space by learning from conflicts. (uv's implementation builds on the [PubGrub](https://github.com/pubgrub-rs/pubgrub) algorithm, originally from [Dart's pub](https://nex3.medium.com/pubgrub-2fb6470504f).)

## Universal lock files require forking the solve

Creating a lock file for a single platform is straightforward: filter out irrelevant markers and solve. But [uv](https://pydevtools.com/handbook/reference/uv.md) aims to produce a *universal* lock file, one that works on Windows, macOS, Linux, across Python versions, all from a single resolution.

This gets hard fast. A project might legitimately require Pydantic 2 on Windows and Pydantic 1 everywhere else. uv handles this by *forking* the resolution: it solves separate sub-graphs for each side of a platform marker, then merges the results. Packages that appear in both forks with compatible versions get unified. Packages that differ get annotated with disjoint markers so that at install time, the right version is selected through a simple graph traversal with no second SAT solve.

The markers themselves create a second NP-hard problem. Testing whether two marker expressions are disjoint (can they both be true?) is also Boolean satisfiability. When resolving a project like [Hugging Face Transformers](https://github.com/huggingface/transformers) with all optional dependencies enabled, individual marker expressions grew to tens of kilobytes before the team built a dedicated marker normalizer based on [algebraic decision diagrams](https://en.wikipedia.org/wiki/Binary_decision_diagram).

Charlie described this forking-and-merging system as the hardest part of building uv.

## Metadata without downloading the package

Before the resolver can do its work, it needs to know each package's dependencies. That metadata isn't always available through the registry API. For packages that only publish source distributions, uv might have to download the source and run `setup.py` just to discover dependencies.

For [wheel](https://pydevtools.com/handbook/reference/wheel.md) archives (which are zip files), uv avoids downloading the full file. It makes a range request for the zip's central directory (an index at the end of the file), finds the metadata file's location, then makes a second range request for just that file. For [PyTorch](https://pytorch.org/) wheels that weigh hundreds of megabytes, this means fetching a few kilobytes instead.

## Versions as single integers

Python version strings are complex: pre-releases, post-releases, local identifiers, epochs. The full representation involves multiple vectors and heap allocations. But over 90% of real-world versions fit into a single 64-bit integer, packed so that larger versions map to larger integers. Version comparison becomes a single `memcmp` instead of a multi-field struct comparison. For resolution-heavy workloads with minimal I/O, this optimization alone delivered a 3-4x speedup.

## Zero-copy deserialization from cache

uv's cache stores unpacked wheel contents and links them into virtual environments using hard links or reflinks. Installing a cached package means creating filesystem links, not copying files. This is why recreating a virtual environment with uv feels instant.

For metadata (version lists, dependency graphs), uv uses zero-copy deserialization via the [rkyv](https://rkyv.org/) library. Data is stored on disk in the same binary layout it will have in memory. Reading it back is a pointer cast, not a parse. Unlike JSON deserialization, which scales linearly with data size, this approach has constant deserialization cost regardless of how large the data grows.

As Charlie put it: "The deserialization does not scale with your data."

## Speed changes the relationship

The performance gap changes how developers work with the tool. Virtual environments become ephemeral: destroy and recreate rather than carefully maintain. Tasks that were CI-only, like full dependency resolution or linting the whole codebase, move into local pre-commit hooks. Rebuilding an environment stops being a thing to dread when it finishes faster than a browser tab switch.

At the time of the talk, uv handled over 10% of all [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) requests and had reached 16 million downloads per month.

The full talk covers additional topics including uv's pip-compatible interface, the install plan system, and how uv discovers Python interpreters. Watch it at [Jane Street's site](https://www.janestreet.com/tech-talks/uv-an-extremely-fast-python-package-manager/) or on [YouTube](https://youtu.be/gSKTfG1GXYQ).
