TL;DR: SemVer is a promise to consumers about what a change can break, encoded as MAJOR.MINOR.PATCH. It binds hardest for published libraries and SDKs. For continuously deployed services the honest practice is strict SemVer for anything others import, git SHA tags plus API compatibility discipline for internal services, because service callers can remain on older releases long after the server changes.
How to approach it
Give each segment its promise first, then split libraries from services. Lumping them together is the trap: a library's consumers upgrade deliberately, a service's consumers are already running against it, so the same version bump means different things.
A strong answer
SemVer encodes intent: PATCH for backwards-compatible fixes, MINOR for backwards-compatible features, MAJOR for changes that can break consumers, once the public API is defined, with pre-release suffixes such as 2.1.0-rc.1 for candidates. The value is not the string. It is the decision it forces before you publish: could this change break someone who depends on us?
CI can automate the declared version intent; it does not prove compatibility. Conventional commit messages drive the bump: fix: produces a PATCH, feat: produces a MINOR, a BREAKING CHANGE: footer produces a MAJOR, and a release job tags main and generates the changelog from history. Consumers pin ranges accordingly, with range semantics checked for their package manager. In npm, ^1.2.3 permits compatible 1.x releases, while ^0.2.3 stops before 0.3.0; ~1.2.3 stops before 1.3.0. These range rules need care below 1.0. A lockfile records resolution under its ecosystem rules; supported platform and installer settings still matter.
Then the honest split:
| Artifact | Strict SemVer? | Why |
|---|---|---|
| Published library or SDK | Yes | Unknown consumers on independent upgrade cycles |
| Internal shared package | Yes, lighter process | Known consumers but real breakage blast radius |
| Deployed service | Git SHA plus API version | Callers can deploy independently; preserve the contract |
Services complicate things because the breaking surface is rarely the binary, it is the interface. So version the contract: /v2/ routes, protobuf packages, event schemas, evolved under compatibility rules such as adding optional fields and never repurposing old ones. Record the Git SHA for traceability and deploy the image by digest. A tag named after a Git SHA is still mutable unless the registry enforces immutability; latest in a deployment manifest is how Friday deploys turn into mysteries.
Set the deprecation window around actual consumer upgrade constraints. A mobile app, an external integration and an internal worker can have very different lifetimes; deploy frequency alone tells you none of them.
A service can use SemVer as release metadata even when deployed continuously. Explain which compatibility promise the number communicates and how you test it. Coordinating one client and server rollout does not cover older clients, queued messages or rollback versions.
Compatibility exercise: an optional field still breaks someone
In a fictional API release, the server adds an optional status_detail field and labels the release MINOR. Most clients ignore unknown fields, but a generated client rejects them. The schema change looked additive while the actual consumer contract was stricter. Test representative supported clients against the candidate before making the compatibility claim; the version label cannot establish it.
Now change a timeout default without changing the response schema. A caller whose operation used to complete after six seconds may fail under a new five-second limit. Decide whether that behaviour belongs to the promised public contract and document the migration. Compatibility includes more than field names.
For the release record, retain the API contract version, source revision and deployed digest separately. During an incident, those answer different questions: what callers were promised, which code was built, and which bytes ran. Exercise an older supported client and the rollback server version against queued messages before declaring the transition safe.
What interviewers probe next
"When do you take a MAJOR deliberately?" End-of-life or security necessity, with a documented deprecation window, telemetry counting remaining old-version consumers, and a migration guide. Breaking silently is the sin; breaking openly is normal engineering.
"How do consumers survive MINOR upgrades?" Ranges plus lockfiles, plus integration tests that run against released versions rather than only against main.
"Is SemVer obsolete once you deploy continuously?" No, but its centre of gravity moved from artifacts to interfaces. The discipline survives as API compatibility rules rather than as image tags.
Common mistakes
Tagging everything v1.x forever while shipping breaking API changes underneath, which turns the number into a lie.
Confusing the Docker tag with the API contract version, then wondering why clients broke after an "identical" deploy.
Explaining the three numbers without mentioning consumers once, which misses that SemVer is a communication protocol, not a naming convention.