DEV Community

Mark
Mark

Posted on

Rethinking DELETE: Idempotency Design and Correct Semantics for Delete Endpoints

In many backend projects, the DELETE endpoint is often treated as the simplest kind of CRUD operation.

The typical implementation looks like this:

DELETE /users/123
Enter fullscreen mode Exit fullscreen mode

And the logic behind it:

  1. Find the user
  2. Delete the data
  3. Return success

If the data doesn't exist:

404 Not Found
Enter fullscreen mode Exit fullscreen mode

This seems intuitive enough.

But once we dig deeper into REST semantics, idempotency, network retries, and distributed systems, we discover that the DELETE endpoint isn't nearly as simple as it looks.

A well-designed DELETE endpoint shouldn't ask:

"Did I actually perform a delete action?"

It should instead ask:

"After this request completes, is the resource in a nonexistent state?"

This shift in perspective is the key to rethinking DELETE.


1. What Is Idempotency?

In the HTTP specification, idempotent means:

Executing the same request once versus multiple times produces the same final effect on the server's state.

For example:

PUT /users/123
Enter fullscreen mode Exit fullscreen mode

Setting the username:

{
    "name": "Tom"
}
Enter fullscreen mode Exit fullscreen mode
  • First call: name = Tom
  • Second call: name = Tom

The final state is the same, so PUT is idempotent.

DELETE is expected to be idempotent as well. For example:

Initial state: User 123 exists

Call Result
1st DELETE /users/123 User 123 no longer exists
2nd DELETE /users/123 User 123 still doesn't exist
3rd DELETE /users/123 User 123 still doesn't exist

Even though the first call actually changed the data, subsequent calls don't change anything further — but the final state remains consistent.

So DELETE is naturally idempotent.


2. A Common Mistake: Treating "No Change" as Failure

Many endpoints are designed like this:

First request:

DELETE /users/123
Enter fullscreen mode Exit fullscreen mode
200 OK
{
    "success": true
}
Enter fullscreen mode Exit fullscreen mode

Second request:

DELETE /users/123
Enter fullscreen mode Exit fullscreen mode
404 Not Found
{
    "success": false,
    "message": "User does not exist"
}
Enter fullscreen mode Exit fullscreen mode

This looks reasonable at first glance, but there's a real problem: did the second request actually fail?

The answer is: no.

Because what is the client's actual goal? It's not to ask the server to:

"Perform one delete action"

It's to ensure:

"User 123 does not exist"

After the second request executes, user 123 still doesn't exist — the goal has already been achieved.

So from DELETE's true semantics, the second request should also be considered a success.


3. The Real Semantics of DELETE: Ensuring the Resource Does Not Exist

Many developers think of DELETE as:

"Delete the data"

But a more accurate way to understand it is:

"Ensure the resource enters a nonexistent state"

For example:

DELETE /currencies/123
Enter fullscreen mode Exit fullscreen mode

This expresses:

"Please make sure Currency 123 does not exist."

Therefore:

Request Before After
1st Exists Does not exist
2nd Does not exist Does not exist
3rd Does not exist Does not exist

Final state: does not exist — consistently, every time.

That is what true idempotency means.


4. Why Should "Already Nonexistent" Also Count as Success?

The biggest reason: networks are unreliable.

Consider a real-world scenario:

  1. Client sends DELETE
  2. Server deletes successfully
  3. But the response is lost in transit
  4. The client doesn't know what happened
  5. So the client sends DELETE again

If the second request returns 404, the client is left confused:

  • Did the first request actually succeed?
  • Do I need to refresh the page?
  • Do I need to show an error to the user?

If DELETE is instead designed so that "already nonexistent" also means success, then:

  • First request → deletion succeeds
  • Second request → the target state is already satisfied

The client doesn't need any extra logic to handle the ambiguity.


5. What Should success Actually Represent?

This is a critical design principle:

success indicates whether the request completed successfully — not whether the data actually changed.

Many systems run into trouble because they conflate these two concepts.

Wrong approach:

{
    "success": false,
    "message": "No data was deleted"
}
Enter fullscreen mode Exit fullscreen mode

Because: not deleting any data does not mean the request failed.

Better approach:

{
    "success": true,
    "data": {
        "deleted": false
    }
}
Enter fullscreen mode Exit fullscreen mode

Meaning:

  • The request executed successfully
  • But it didn't produce a data change

These are two separate concepts, and they should be kept separate.


6. Recommended DELETE Response Design

For simple resources

Examples: tags, categories, currencies, personal settings.

Recommended:

DELETE /currencies/123
Enter fullscreen mode Exit fullscreen mode
{
    "success": true,
    "message": "Deleted successfully"
}
Enter fullscreen mode Exit fullscreen mode

Regardless of whether it's:

  • the first deletion,
  • a repeated deletion, or
  • the resource was already gone,

the response is always success.

When you need to know whether a change actually occurred

{
    "success": true,
    "data": {
        "deleted": true
    }
}
Enter fullscreen mode Exit fullscreen mode
  • First call: "deleted": true
  • Second call: "deleted": false

Note that success is true in both cases, because the request itself succeeded.


7. Why Are Orders and Contracts Different?

You might ask: shouldn't order deletion work the same way?

Answer: not necessarily.

Orders, contracts, and approval records are no longer simple resources. They represent:

  • Business facts
  • Legal records
  • Financial data
  • Audit information

Deletion itself becomes a meaningful business action.

For example, an order typically flows through states:

Created → Paid → Cancelled
Enter fullscreen mode Exit fullscreen mode

And a contract:

Draft → Signed → Terminated
Enter fullscreen mode Exit fullscreen mode

In these scenarios, a real DELETE is usually never performed. Instead, the system records a state change, such as:

status = CANCELLED
Enter fullscreen mode Exit fullscreen mode

or:

deleted = true
deleted_by = xxx
deleted_time = xxx
Enter fullscreen mode Exit fullscreen mode

Because the system needs to know:

  • Who deleted it
  • Why it was deleted
  • When it was deleted
  • What state it was in before deletion

8. When Do You Need to Know Whether the Resource Existed Before Deletion?

Sometimes you do. For example:

Audit systems

When an administrator deletes a contract, the system needs to record:

Deleted contract A
Operator: Zhang San
Time: 2026-08-11
Enter fullscreen mode Exit fullscreen mode

Here, whether the resource genuinely existed beforehand matters a great deal.

Financial systems

When deleting a payment record, the system must confirm whether the payment record genuinely existed — otherwise it could cause accounting discrepancies.

But this kind of requirement should not be expressed through success = false. It should instead be handled through:

  • Audit logs
  • Business status fields
  • Dedicated response fields

9. Practical Design Recommendations

For ordinary CRUD resources (users, categories, tags, settings):

  • DELETE means "ensure the resource does not exist"
  • Nonexistence is not a failure

For business objects (orders, contracts, approvals, payments):

  • Use state transitions
  • Avoid actual deletion

10. Summary

A mature DELETE endpoint design is not this:

Call the delete method
    ↓
Check whether data was actually deleted
    ↓
Return success or failure
Enter fullscreen mode Exit fullscreen mode

It should instead be this:

Understand the client's goal
    ↓
Bring the resource to that target state
    ↓
Return the outcome of the request
Enter fullscreen mode Exit fullscreen mode

Core idea: the goal of DELETE is not the act of deletion — it's the final state.

Therefore:

  • DELETE should be idempotent
  • A nonexistent resource is not necessarily a failure
  • success reflects whether the request completed, not whether the data changed
  • Whether data actually changed is a business-level result
  • Business-critical data should not be casually hard-deleted

In one sentence:

Good API design doesn't describe what the server did — it describes what state the client wants to reach.

This is exactly why a seemingly simple DELETE endpoint can reveal so much about a backend engineer's understanding of REST, distributed systems, and business modeling.

Top comments (0)