Skip to main content

What is the difference between PUT and PATCH?

PUT vs PATCH - PUT replaces the entire resource with the body you send; PATCH updates only the fields you specify.

Theory

TL;DR

  • PUT = full replacement: missing fields get overwritten to null or a schema default
  • PATCH = partial update: only specified fields change, the rest stay untouched
  • Analogy: PUT is handing over a brand new passport; PATCH is sticking a new sticker on the existing one
  • Idempotent: PUT always yes. PATCH - not guaranteed, depends on implementation
  • Rule: client holds full state, small resource → PUT. Large resource, few changes → PATCH

Quick example

http
// PUT /users/123 - replaces the ENTIRE user object PUT /users/123 Content-Type: application/json { "id": 123, "name": "Alice", "email": "alice@new.com", "phone": null } // If original had phone: "123-456", it is now null // PATCH /users/123 - updates ONLY email, phone stays intact PATCH /users/123 Content-Type: application/json { "email": "alice@new.com" } // phone: "123-456" is preserved

PUT overwrites unspecified fields. PATCH leaves them alone.

Key difference

PUT treats the request body as the new authoritative state of the resource. Whatever is missing from the body gets overwritten with null or a server-defined default, per RFC 7231. PATCH applies a diff: only named fields change, everything else is preserved. This is defined in RFC 5789. The practical result is that a careless PUT destroys data with no error thrown.

When to use

  • Client controls the full resource and it is small → PUT (config overwrite, CRUD form that always sends every field)
  • Large resource with many fields, updating just one or two → PATCH (user profiles, billing details)
  • Client cannot afford to fetch the full current state first → PATCH (saves bandwidth)
  • Need an idempotent full reset → PUT
  • Concurrent updates are possible → PATCH with JSON Merge Patch (RFC 7396) or JSON Patch (RFC 6902)

Comparison table

AspectPUTPATCH
BodyComplete resource representationPartial changes only
Unspecified fieldsOverwritten (null or schema default)Preserved
IdempotentYesNot guaranteed
Content-Typeapplication/jsonapplication/json-patch+json or application/merge-patch+json
RFCRFC 7231RFC 5789
When to useFull state replacement, small resourcesSparse updates, large resources

How the server handles each

A server receiving PUT replaces the stored resource entirely and saves the new body. Fields absent from the body get set to null or schema defaults. This is why a PUT with only a subset of fields loses the rest without any warning.

For PATCH, the server applies only the named keys. Libraries like fast-json-patch in Node.js handle this through operations: {"op": "replace", "path": "/email", "value": "new@mail.com"} touches only that path in the JSON tree.

Common mistakes

Mistake 1: using PUT for a partial update

javascript
// Wrong: PUT omits phone, so server sets phone to null fetch('/users/1', { method: 'PUT', body: JSON.stringify({ name: 'Alice' }) }); // Result: phone is erased. No error, no warning. // Fix: use PATCH, or fetch the full object first and PUT the merged result

Mistake 2: non-idempotent PATCH handler

javascript
// Wrong: increments on every call - retrying this doubles the count app.patch('/items/:id', (req, res) => { item.count += req.body.amount; }); // Right: replace the value directly - same result on every retry app.patch('/items/:id', (req, res) => { item.count = req.body.count; });

Mistake 3: wrong Content-Type for PATCH

javascript
// Wrong: some servers treat application/json as a full PUT headers: { 'Content-Type': 'application/json' } // Correct for JSON Patch ops array: headers: { 'Content-Type': 'application/json-patch+json' } // Correct for merge patch (object diff, null means delete): headers: { 'Content-Type': 'application/merge-patch+json' }

Mistake 4: PUT without If-Match in concurrent environments

A blind PUT overwrites changes made by another client between your read and your write. Add an ETag and send If-Match: "abc123" with the PUT. The server returns 412 Precondition Failed if the resource changed, forcing a re-fetch.

Real-world usage

  • Firebase: update() = PATCH semantics (preserves fields), set() = PUT semantics
  • Stripe API: PATCH /customers/{id} for sparse billing updates
  • Strapi CMS: uses JSON Patch for partial content updates
  • React Query / SWR: useMutation with PATCH for optimistic UI
  • Express.js + MongoDB: replaceOne for PUT (full replace), updateOne with $set for PATCH

Follow-up questions

Q: Why is PUT idempotent but simple PATCH not always?
A: PUT uses the full body as the new state, so ten identical requests produce the same result. Simple merge-patch is also idempotent when it replaces a value. The problem is additive logic (increment, append) inside the handler. Use JSON Patch with explicit replace operations to guarantee idempotence.

Q: What is the difference between JSON Patch and JSON Merge Patch?
A: JSON Patch (RFC 6902) is an array of operations: add, remove, replace, test. JSON Merge Patch (RFC 7396) is a simpler object diff where null means delete the field. Merge Patch is easier to write but lacks operation ordering and atomic test-and-set support.

Q: How do you design a safe PATCH system for concurrent editing?
A: Use ETags and the If-Match header. The client reads the resource and gets an ETag, then sends PATCH with If-Match: "<etag>". If someone else changed the resource, the server returns 412. The client re-fetches and reapplies. For collaborative editing, add JSON Patch test operations to assert preconditions before each mutation.

Q: When does PUT break idempotence?
A: When the server generates or modifies fields on every write regardless of body, like updatedAt timestamps. The body is identical but the stored resource differs after each call.

Examples

Basic: updating a user profile

javascript
// PUT - must include ALL fields or data is lost app.put('/users/:id', async (req, res) => { // replaceOne overwrites the entire document await User.replaceOne({ _id: req.params.id }, req.body); res.json(await User.findById(req.params.id)); }); // PATCH - send only what changed app.patch('/users/:id', async (req, res) => { // $set with partial body; unmentioned fields stay intact await User.updateOne({ _id: req.params.id }, { $set: req.body }); res.json(await User.findById(req.params.id)); });

PUT uses replaceOne, which overwrites the whole document. PATCH uses updateOne with $set, so only the sent fields change. I have seen junior devs ship PUT handlers that quietly erased phone numbers for weeks before anyone noticed.

Intermediate: JSON Patch with concurrent safety

javascript
const jsonPatch = require('fast-json-patch'); app.patch('/users/:id', async (req, res) => { const user = await User.findById(req.params.id); const clientEtag = req.headers['if-match']; if (clientEtag && clientEtag !== user.etag) { return res.status(412).json({ error: 'Precondition Failed' }); } // req.body is a JSON Patch ops array const patched = jsonPatch.applyPatch(user.toObject(), req.body).newDocument; await User.replaceOne({ _id: req.params.id }, patched); res.json(patched); }); // Client sends: // PATCH /users/123 // If-Match: "etag-abc" // Content-Type: application/json-patch+json // [{ "op": "replace", "path": "/email", "value": "new@mail.com" }]

The test operation adds another safety layer. {"op": "test", "path": "/email", "value": "old@mail.com"} throws before the replace if the current value does not match, blocking stale writes before they happen.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?