What does a constraints file passed to pip with `-c` do that a requirements file with `-r` does not?
answer
- One file demands, the other only bounds
- It creates no work for the resolver
- Useful for a package you do not import
- Matching nothing is a valid outcome
- Partial bound, never a complete record
basics
~20 sA constraints file only bounds versions: it says that if a distribution ends up being installed, it must satisfy this specifier. It never causes an installation. A requirements file passed with -r also decides what gets installed.
solid answer
~50 sBoth files hold requirement specifiers, but they play different roles in the resolution. Entries in a `-r` requirements file are **demands**: pip must install each of them. Entries in a `-c` constraints file are **bounds**: they restrict the version pip may choose for a distribution, but on their own they request nothing, so a constraint naming a package that never enters the graph is simply inert. That is exactly what makes it useful for transitive dependencies -- you can force a bad indirect release out of the resolution without adding a direct dependency you do not actually import. Constraints are also compositional: one shared file of organisation-wide floors can be passed alongside every service's own requirements. The limits follow from the semantics: constraint entries must be plain named requirements, editable entries are rejected, and because a constraint can silently match nothing, it is a bounding mechanism, never a record of a resolution.
go deeper
Recall the distinction in one line: a requirements file installs things, a constraints file only limits which version is acceptable if something else asks for the package. Knowing the two flags apart is enough here.
Explain the resolver semantics and the motivating case -- bounding a transitive dependency you do not import -- plus the consequence that an unmatched constraint is silent rather than an error.
Show where it fits operationally: a shared floor file across services, why it is not a lock, and how you stop constraints accumulating into an invisible pile nobody dares remove.
Decide whether centrally-published constraints are the right lever at all compared with an internal index, automated upgrade proposals, or per-service locks, and who owns the file once dozens of teams depend on it.
### The one-sentence difference `-r` says *install these*. `-c` says *and if you install this, it must be this version*. Everything else follows from that. ### Why the distinction matters The motivating case is a transitive dependency. Your service imports `frame-tools`, which in turn requires `clockdrift`. A `clockdrift` release goes out with a defect. You do not import `clockdrift`, so adding it to your own dependency list would be a lie: it announces a direct relationship that does not exist, and a future reader has no way to tell it was added only to dodge a bad release. Written as a constraint instead, the intent is legible -- *whatever pulls this in, do not let it be that version* -- and the entry becomes inert the day nothing needs the package any more. The second case is scale. A platform team can publish one constraints file expressing organisation-wide floors -- minimum versions that carry security fixes -- and every service passes it alongside its own requirements. Nobody has to enumerate which services happen to depend on what; the file only bites where the package is present. ### How pip consumes it ``` python -m pip install -r requirements.txt -c constraints.txt ``` Both flags may be repeated, and the constraint entries are merged into the resolution as extra bounds. A constraints file is a plain text file of requirement specifiers, so ranges, exact pins and environment markers all work: `clockdrift!=0.4.3`, `clockdrift>=0.4.4`, `clockdrift==0.4.2; python_version < "3.12"`. The restrictions come straight from the semantics. A constraint has to name a distribution -- an entry pip cannot associate with a name has nothing to bound. Editable entries are rejected, because installing a local project in place is a demand, not a bound. And a constraint that matches nothing produces no error, since nothing being installed is a perfectly valid outcome. ### Where teams misuse it **As a lockfile.** Copying pinned output into a constraints file and installing with only `-c` installs nothing at all -- there are no demands. Even paired with `-r`, the result is weaker than a lock: because unmatched constraints are silent, the file can rot for months while everyone believes it is pinning something. A lock is a *complete* record of a resolution; a constraints file is a *partial* bound on one. **As a substitute for fixing the direct dependency.** If a constraint is the only thing keeping a service on an old release of something it genuinely imports, that belongs in the project's own dependency ranges where a reviewer will see it. **Expecting it to force an upgrade.** A floor in a constraints file does not install the package or pull it in; it only rules out versions below the floor when something else asks for it. If the requirement is that a package be present at a minimum version, that is a requirement, not a constraint. ### The mental model to keep Think of the resolver as solving a set of equations. `-r` adds equations that must be satisfied. `-c` narrows the domain of a variable without asserting that the variable appears in the system. Both shape the answer; only one of them can create work.
- Why can a constraints file not replace a lockfile even when every entry is pinned with `==`?Because constraints never demand an installation and never have to be complete. A pinned constraint on a package that no longer enters the graph is silently ignored, so the file can drift out of relevance with no signal, and it says nothing about the packages that *are* installed but absent from it. A lock records the entire resolved set; a constraints file bounds an arbitrary subset of it.
- You need every service to move off a vulnerable release of an indirect dependency. Constraint or requirement?A constraint expressing the floor, published centrally and passed alongside each service's own requirements. It bites wherever the package is present, stays inert where it is not, and does not fabricate a direct dependency in projects that never import it. If a service must have the package installed regardless, that specific service adds a real requirement.
saying these in an interview costs you the question
- Thinks a constraints file installs the packages it names
- Uses a constraints file as a lockfile substitute
- Expects a version floor in constraints to trigger an upgrade
- Adds a fake direct dependency to pin a transitive one
- Assumes an unmatched constraint raises an error