❓Quick question:
Is a DELETE endpoint that returns
404on subsequent calls idempotent?
If you said 'no' because the response changed, this article is for you.
Idempotence is a concept that is often misunderstood, sometimes even by developers with many years of experience.
What is Idempotence
If we take the Wikipedia definition:
Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.
So as we can see, it's originally a mathematical concept that got translated to Computer Science.
In Computer Science, the term idempotence I like to describe it as:
Idempotence in computer science is the property of an operation whereby it can be applied multiple times without changing the result beyond the initial application
Example GET operation
Normally, a GET operation is idempotent, since it does not imply any change in the database. So, for example, you can have an endpoint like:
/api/v1/users
And it does not matter how many times you call it, it will return the same output and produce no side effect.
POST, PUT, PATCH and DELETE operations
This is where the confusion comes from. People by default think that POST, PUT, and PATCH endpoints are not idempotent by definition, and the answer is that it depends on the actual implementation of the endpoints.
POST operations
Let's look for example the case of a POST endpoint to create Users.
Imagine that we have the following model:
class User {
final UUID id;
final String username;
}
And we call the endpoint:
> POST api/v1/users
{
"username": "manuelarte"
}
Getting a response:
200 OK
{
"id": "f2f7734c-1082-43cf-9b66-9161349ef154",
"username": "manuelarte"
}
❓Is this endpoint idempotent?
With this information alone, we cannot determine it yet.
Scenario A: A new user is created
If we call it again with the same input and we get:
200 OK
{
"id": "c6fab354-c559-460b-a66c-5d0bad8f6aba",
"username": "manuelarte"
}
Then the endpoint is definitely not idempotent❌, since it is creating new users for each request with the same data.
Scenario B: Same response as first response
And, what happens if we call the endpoint again with the same data and the response is the same as in the first call:
200 OK
{
"id": "c6fab354-c559-460b-a66c-5d0bad8f6aba",
"username": "manuelarte"
}
Then, ✅the endpoint is idempotent, because it's not creating a new entry in the database, and in this case is returning the already existing entry.
So, it does not matter how many times you call this endpoint with this data, the state remains the same.
This is the scenario that most people understand for idempotence. If you do multiple request with the same data, you always get the same response, same HTTP response code and same body.
Scenario C: Consecutive calls returns different response
But, let's imagine that the endpoint returns the following:
400 Bad Request
{
"code": "USER_CREATED",
"message": "user already created"
}
So now, instead of 200, it is returning 400, and explaining that there is already a user with that username created.
❓Do you think that this endpoint is, then, idempotent?
And the answer is (and this is where many senior developers and/or architects get idempotence wrong) that this implementation is also idempotent, because the second and subsequent calls do not change state. In this case, with only one user created.
So idempotence is about the final state in the database, and not about the HTTP verb or HTTP response code.
DELETE endpoints
To clarify more, let's put another example, this time using a DELETE endpoint:
DELETE /api/v1/users/{id}
If we call this endpoint with the user id, then this endpoint is idempotent independently of the response code, because after the first successful deletion, the resource no longer exists, and further calls do not change the state.
So after that initial call, next calls can have outputs like NO CONTENT 204 or NOT FOUND 404 and the endpoint is still idempotent.
But you could have an endpoint like this
DELETE /api/v1/users/last
That would delete the latest user created, and even that it could potentially return, e.g. NO CONTENT 204 for every call, the endpoint would not be idempotent.
So, as an example, each call changes which user is considered "last". The first call deletes user 5, the second deletes user 4, and so on. The state changes with each call.
Summary and takeaways
Hopefully you are able to understand that idempotence is about consecutive calls with the same data leads to no change in the state.
Understanding that idempotence is about state, not responses, is crucial for:
- API Design: Building robust systems that handle retries safely
- Client Implementation: Knowing how to handle different response codes
- Contract Testing: Writing accurate tests for API behavior
Next time you're designing or consuming an API, remember:
- idempotence = State doesn't change after the first request
- HTTP methods suggest idempotence, but implementation guarantees it
idempotence isn't about what the server says—it's about what the server does.
For more references check RFC-9110, and how to implement idempotence with headers
Top comments (0)