skip to content

In a composer.json constraint, how does ^1.2 differ from ~1.2, and how do ^1.2.3, ~1.2.3 and ^0.3 behave?

level: middleimportance: must knowfreq 65%

answer

  1. two parts vs three parts
  2. tilde lets the last digit rise
  3. caret fixes the major
  4. pre-1.0 caret is stricter
  5. 1.2.* is not ~1.2

basics

~10 s

In Composer, ^1.2 and ~1.2 are the same range, >=1.2.0 <2.0.0. They differ with three parts: ~1.2.3 means >=1.2.3 <1.3.0 while ^1.2.3 means >=1.2.3 <2.0.0. For 0.x, ^0.3 means >=0.3.0 <0.4.0.

solid answer

~40 s

The tilde lets only the **last digit you wrote** go up; the caret keeps the **major** fixed and allows every non-breaking release. With two parts they coincide: `~1.2` and `^1.2` both mean `>=1.2 <2.0.0`. With three parts they split: `~1.2.3` is `>=1.2.3 <1.3.0`, patch releases only, while `^1.2.3` is `>=1.2.3 <2.0.0`. Below 1.0 the caret treats the minor as breaking: `^0.3` is `>=0.3.0 <0.4.0` and `^0.0.3` is `>=0.0.3 <0.0.4`. The caret is Composer's recommended operator for libraries and is what `composer require` writes by default, for example `^2.1` for a 2.1.x release. Watch the look-alike: `1.2.*` is `>=1.2 <1.3`, much narrower than `~1.2`.

code

json · 8 lines
json
{
    "require": {
        "acme/parser": "^1.2.3",
        "acme/legacy": "~1.2.3",
        "acme/young": "^0.3",
        "acme/exact": "1.2.*"
    }
}

go deeper

for a junior

Recall that ^ keeps the major fixed and that composer require writes a caret constraint for you.

for a middle

Explain why ~1.2 and ^1.2 are the same range, how ~1.2.3 narrows to patches, and how the caret narrows for 0.x versions.

for a senior

Pick operators deliberately: caret with a tested lower bound for libraries, three-part tilde or 1.2.* when a package breaks in minors, and no unbounded ranges.

for a principal

Set a house rule for constraints across repositories and enforce it with composer validate in CI, so constraint style stops being a per-developer habit.

## What a Composer constraint is In `composer.json`, the string after a package name in `require` is a **version constraint**, not a version. Composer lists every tagged release of the package, keeps those matching all constraints in the project (yours and every other package's), and picks the highest one allowed. The two **next significant release operators**, `~` (tilde) and `^` (caret), are the everyday way to say "this version or anything compatible with it". ## The tilde: the last digit you wrote may rise Composer's rule for `~` is that the **last specified component** may increase, and everything before it is fixed: - `~1.2` means `>=1.2 <2.0.0` — the `2` may rise, the `1` is fixed. - `~1.2.3` means `>=1.2.3 <1.3.0` — only the patch may rise. - `~1` is treated like `~1.0`, i.e. `>=1.0 <2.0` — the tilde never lets the major rise. So the meaning of a tilde depends on **how many parts you wrote**. That is exactly the trap interviewers set. ## The caret: the major is fixed The `^` operator follows semantic-versioning intent: any release that should not break you is allowed. - `^1.2` and `^1.2.3` both stop before `2.0.0`: `>=1.2 <2.0.0` and `>=1.2.3 <2.0.0`. - For **pre-1.0** versions the caret treats the leftmost non-zero part as the breaking one: `^0.3` is `>=0.3.0 <0.4.0`, and `^0.0.3` is `>=0.0.3 <0.0.4`. Composer's documentation calls the caret the recommended operator for maximum interoperability in library code, and `composer require vendor/pkg` without a constraint writes one: for a stable release `2.1.4` it writes `^2.1`; for `0.3.2` it keeps the patch and writes `^0.3.2`. ## Side by side | Constraint | Equivalent range | Allows | |---|---|---| | `~1.2` | `>=1.2 <2.0.0` | minors and patches | | `^1.2` | `>=1.2 <2.0.0` | minors and patches (same as above) | | `~1.2.3` | `>=1.2.3 <1.3.0` | patches only | | `^1.2.3` | `>=1.2.3 <2.0.0` | minors and patches | | `~0.3` | `>=0.3 <1.0` | `0.4`, `0.5`, … | | `^0.3` | `>=0.3.0 <0.4.0` | patches of 0.3 only | | `1.2.*` | `>=1.2 <1.3` | patches of 1.2 only | | `1.2.3` | exactly `1.2.3` | nothing else | Two rows deserve a second look. `~0.3` is **looser** than `^0.3`, because the tilde does not know that 0.x minors are breaking. And `1.2.*` looks like `~1.2` but stops before 1.3 instead of 2.0. ## How Composer normalises these internally Composer turns every constraint into explicit bounds on four-part versions with a stability suffix. The documentation shows, for example, that `~1.3` becomes `>=1.3.0.0-dev <2.0.0.0-dev` and `1.4.*` becomes `>=1.4.0.0-dev <1.5.0.0-dev`, while an exact `1.2.3` becomes `=1.2.3.0-stable`. The `-dev` on the bounds is why the edges of a range behave predictably: the upper bound `<2.0.0.0-dev` sits below every 2.0 pre-release, so nothing from the next major sneaks in. You rarely write these forms yourself, but they appear in solver error messages, and reading them is faster than guessing. When a constraint cannot be parsed at all, `composer validate` reports it as an invalid version constraint, and it warns when a constraint cannot possibly match anything. ## Pre-releases and stability A caret or tilde range does not reach into the next major's pre-releases: `~1.2` will not install `2.0-beta.1`, even though that beta sorts before `2.0`. Whether a range accepts betas or RCs **inside** it depends on `minimum-stability` and stability flags, a separate mechanism. ## Choosing between them 1. **Libraries**: prefer `^` with the lowest version you actually test against, so consumers can resolve alongside everything else. 2. **Applications**: `^` is also the normal choice; the lock file, not the constraint, is what freezes the exact version you deploy. 3. Use `~x.y.z` when you deliberately want **patch releases only** from a package that does not keep its minor releases compatible. 4. Avoid exact versions and unbounded ranges (`>=1.0`, `*`) in `require`; `composer validate` warns about both. Why caret and tilde exist at all, and what semantic versioning promises, is general dependency theory; the Composer-specific content is the exact ranges above and the fact that the two operators only diverge when three parts or a zero major are written.

  • Someone coming from npm reads ~1.2 in a composer.json as 'patch releases of 1.2 only'. Are they right?
    No. npm's tilde on `1.2` allows only 1.2.x patches, but Composer's tilde lets the last written digit rise, so `~1.2` accepts 1.3, 1.9 and so on up to, not including, 2.0.0. To get patch-only behaviour in Composer, write three parts, `~1.2.0`, or `1.2.*`.
  • Why does `composer require` write ^0.3.2 for a 0.3.2 release but ^2.1 for a 2.1.4 release?
    Composer drops the patch number for stable 1.0+ releases so the constraint can move through minors: `^2.1` allows everything below 3.0. For a zero major it keeps the patch, because `^0.3` would already cap at 0.4.0 and dropping the patch would only widen the lower bound, so `^0.3.2` keeps the lower bound at the release you installed.
  • What does ~1 mean in Composer?
    It is treated like `~1.0`, so it means `>=1.0 <2.0`. The tilde has an explicit exception for the major number: it never allows the major to increase, even when the major is the only part written.

A tilde is a ruler you lay down at the last digit you wrote: whatever you wrote last may slide, everything to its left is nailed down. A caret always nails down the major, however many digits you wrote.

saying these in an interview costs you the question

  • ^1.2 and ~1.2 allow different ranges in Composer
  • ~1.2 allows only patch releases of 1.2
  • ^0.3 allows anything below 1.0
  • 1.2.* is the same as ~1.2
  • The caret constraint lets Composer install the next major's betas