Building Modern, Resilient Architectures
In a distributed system, failures are inevitable. A client might send a request, but the network could fail before it receives a response. Did the server process the request? The client doesn't know. The natural reaction is to retry the request.
However, retrying a request can be dangerous if the operation is not idempotent.
An operation is idempotent if making the same request multiple times produces the same result as making it a single time. In other words, repeated requests do not have additional side effects.
Let's look at some examples:
GET /users/123: This is naturally idempotent. Retrieving a user's data multiple times doesn't change the data.DELETE /users/123: This is also idempotent. The first request deletes the user. Subsequent requests will likely return a "404 Not Found" error, but they don't change the state of the system further. The user is still deleted.PUT /users/123 with a full user object: This is idempotent. PUT is defined as a complete replacement of a resource. Sending the same user object multiple times will just overwrite the resource with the same data.POST /users to create a new user: This is NOT idempotent. If you send this request multiple times, you will create multiple new users.PATCH /users/123 with { "age": user.age + 1 }: This is NOT idempotent. Each request will increment the user's age.PATCH /users/123 with { "age": 30 }: This IS idempotent. Setting the age to 30 multiple times has the same result as setting it once.Asynchronous systems that use message queues and retry mechanisms rely on idempotency to function correctly.
Consider a payment processing service that consumes messages from a queue.
{"user_id": "abc", "amount": 10.00}.If the payment processing logic is not idempotent, the service will charge the user a second time. This is a critical bug. If the logic is idempotent, it will recognize that this payment has already been processed and will not charge the user again.
The key to making non-idempotent operations safe to retry is to introduce a unique identifier for each transaction, known as an idempotency key.
The Workflow:
Idempotency-Key: <some-uuid>).Example: Creating a User
The non-idempotent POST /users can be made idempotent with this pattern.
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000.POST /users with the user data and the header.201 Created with the new user object), and associates it with the key. It then returns the response.201 Created response without creating another user.This ensures that even if the client retries the POST request multiple times, only one user will be created.
In a system design interview, when you introduce message queues or discuss retry logic, you must also discuss idempotency. It's a critical concept for building robust and reliable distributed systems. Explaining how you would use an idempotency key to prevent duplicate processing is a strong signal that you have experience with real-world system design challenges.