What you'll learn
- Quick answer
- What 'Consistency' Even Means in a Storage System
- How S3 Used to Behave (Before December 2020)
- The December 1, 2020 Change: Strong Consistency for Everything
- Why This Was a Genuinely Hard Problem to Solve
- What Strong Consistency Does NOT Give You
- What To Actually Do With This As a Developer
- FAQ
Quick Answer
Quick Answer: Since December 2020, Amazon S3 gives every read strong read-after-write consistency, automatically, for every operation (PUT, overwrite, DELETE, and LIST) — before that, overwriting or deleting an object could briefly return the old version if you read it right after. For almost all day-to-day use this removes a whole category of race-condition bugs, but it does not turn S3 into a transactional database.
What 'Consistency' Even Means in a Storage System
Every storage system that keeps more than one copy of your data has to answer one awkward question: if you write something and then immediately read it back, are you guaranteed to see what you just wrote?
Imagine two people sharing a whiteboard, except they're in different rooms and a courier carries updates between them. Person A writes "meeting at 3pm" on their board. Person B asks the courier "what does the board say?" a second later. If the courier hasn't delivered the update yet, Person B gets a stale answer — "meeting at 2pm," the old value. That's eventual consistency: the system will catch up eventually, but there's a window where a read can return old data.
Strong consistency is the opposite guarantee: once a write is acknowledged as successful, every subsequent read is guaranteed to see that write (or a newer one) — never anything older. There's no courier delay, or the system refuses to answer until the delay is gone.
In code terms, the difference looks like this against an object store:
// Upload a new build artifact
await s3.putObject({ Bucket: "builds", Key: "app.js", Body: newBuild }).promise();
// Immediately try to read it back (e.g. a deploy script verifying the upload)
const result = await s3.getObject({ Bucket: "builds", Key: "app.js" }).promise();
// Under eventual consistency: this COULD return the previous app.js, or even a 404
// Under strong consistency: this is guaranteed to return the build you just uploaded
This isn't an academic distinction. Deploy scripts, config reload pipelines, and any "write then immediately verify" pattern depend entirely on which guarantee the underlying store actually gives you. Get it wrong and you'll chase a bug that only reproduces "sometimes, under load," which is the worst kind of bug to debug.
How S3 Used to Behave (Before December 2020)
For most of its existence, S3 had a split personality when it came to consistency, and it's important to understand because a lot of blog posts, Stack Overflow answers, and even some internal docs people still copy-paste from were written during this era.
- New object PUTs (uploading a key that didn't exist before) were always read-after-write consistent. There was no old version to race against, so the first successful read after a successful write always returned the new data.
- Overwrites of an existing object and DELETEs were eventually consistent. If you overwrote
config.jsonand then read it back within the same second, you might get the old bytes, the new bytes, or (for deletes) still see the "deleted" object in a LIST call for a short window. - LIST operations inherited the same eventual behavior — a bucket listing could lag behind the actual current set of keys.
This produced a specific, recognizable class of bug. Teams running config-reload pipelines would overwrite a settings file in S3 and then have a service immediately re-fetch it, only to occasionally load the stale version and silently run with outdated config for a few requests. Static site deploy pipelines that overwrote index.html and new hashed asset files sometimes served a half-updated page — new HTML referencing a JS bundle hash that hadn't "caught up" to being readable yet.
The folklore fix, which you'll still find in older tutorials, was to add an artificial delay or a retry-with-backoff loop after every overwrite before trusting a subsequent read — "wait two seconds and try again" as a workaround baked directly into deploy scripts. It worked most of the time, which is exactly why it was dangerous: it made the failure rare enough to ship, not rare enough to never happen.
The December 1, 2020 Change: Strong Consistency for Everything
On December 1, 2020, AWS announced that S3 now delivers strong read-after-write consistency automatically, for every request type, with no changes required on your end.
The specifics that matter from that announcement:
- It covers PUTs of new objects, overwrites of existing objects, DELETEs, and LIST operations — the entire set that used to be eventually consistent is now strongly consistent.
- It applies automatically to every bucket, old and new, in every region — including AWS GovCloud (US) and the AWS China regions. There is no setting to enable, no API version to opt into, and no migration step.
- AWS explicitly stated there is no additional cost and no trade-off in performance or availability to get this guarantee — it isn't a slower "strict mode" you pay extra for.
- It removed the need for the retry-and-hope patterns and "consistency workaround" infrastructure that had become a quiet tax on S3-backed pipelines for over a decade.
After a successful write, the very next read of that object — from any process, anywhere — is guaranteed to return the latest version. The same guarantee extends to listing a bucket right after writing into it.
Why This Was a Genuinely Hard Problem to Solve
It's worth understanding why this took until 2020, because it tells you something true about distributed systems in general, not just S3.
S3 doesn't store your object on one disk. For its famous "eleven nines" (99.999999999%) durability promise, every object is replicated across multiple devices, in multiple facilities, within a region. The moment you have more than one copy of the truth, you have a choice to make: let any copy answer a read request immediately (fast, but a copy might be stale), or make every read check in with some authoritative source first (consistent, but potentially slower or less available during network hiccups). This tension is the practical, everyday version of what the CAP theorem describes at a theoretical level.
AWS has not published the exact internal mechanism it uses to deliver strong consistency without sacrificing the performance and availability S3 is known for — that's a trade secret, not public documentation. What is publicly confirmed is the outcome: S3 now has some mechanism (conceptually, systems like this typically rely on a consistent, authoritative metadata index that every read and write has to agree with, rather than letting replicas answer independently) that ensures no read can return a result predating the most recent completed write, region-wide, without the customer-visible cost of latency or availability that naive approaches to strong consistency usually carry.
The honest takeaway for a developer: you get to rely on the guarantee without needing to know (or being able to know) exactly how S3 pulls it off underneath.
What Strong Consistency Does NOT Give You
This is the part that trips people up, because "strongly consistent" sounds like it should mean "safe for anything," and it doesn't.
It is not a transaction
If your code updates three related objects — say, a product JSON file, a thumbnail image, and a search-index entry — S3's consistency guarantee applies to each object individually. There is no multi-object atomicity. One write can succeed while another fails, and S3 will not roll anything back for you. If you need "all three or none," you need to build that logic yourself (or use a database that actually supports transactions for the parts that need it).
It does not resolve concurrent writers
If two processes overwrite the same key at roughly the same time, strong consistency just guarantees that whoever reads it afterward sees one of those writes — not a merge of both, and not necessarily the one you expected. It's still "last write wins," and the earlier write is simply gone with no trace. This is exactly the gap that S3 Conditional Writes, added in November 2024, were built to close: you can now pass an If-Match header with an object's current ETag on a PutObject call, and S3 will reject the write if the object changed since you last read it — a real compare-and-swap, at no extra cost, for workloads where two writers racing on the same key is an actual risk (distributed training checkpoints, parallel ETL jobs, anything doing read-modify-write on a shared file).
It is a single-region guarantee
Cross-Region Replication (CRR) between S3 buckets is still asynchronous. If you write to a bucket in ap-south-1 and immediately read the replicated copy in us-east-1, you can absolutely get a stale result — the strong consistency promise doesn't stretch across that replication link.
What To Actually Do With This As a Developer
Translating all of this into decisions you'll actually make:
- Remove old retry-and-wait hacks. If your deploy scripts or pipelines still have an artificial sleep or a "retry 3 times" loop purely to work around stale reads after an S3 write, that workaround is dead weight today — read-after-write is guaranteed without it.
- Still enable Versioning if you need rollback. Strong consistency tells you the latest write is always visible; it does nothing to help you recover an overwritten object. Versioning is a separate, explicit feature for that.
- Use Conditional Writes if two writers could race on the same key. Strong consistency alone doesn't prevent lost updates —
If-Match/If-None-Matchdoes. - Don't assume the guarantee follows your data cross-region. Treat replicated regions as eventually consistent unless you've confirmed otherwise.
- Trust LIST again. A bucket listing right after a write is now reliable, which matters for any "does this file exist yet" check your code does before proceeding.
If you're building anything on top of S3 as part of a course project or a production pipeline, this is one of the few places where "the cloud provider just fixed it for free" is literally true — you just have to know it happened to stop coding around a problem that no longer exists. For more systems-design fundamentals like this, browse the rest of the Priodemy blog.
Frequently Asked Questions
Is S3 ACID compliant now that it's strongly consistent?
No. Strong consistency means a read after a write sees that write — it says nothing about multi-object transactions. S3 still has no cross-object atomic commit, so updating several related objects together is still something you have to coordinate yourself.
Does the December 2020 change apply to buckets I created years ago?
Yes. AWS applied it automatically to every existing bucket in every region, with no migration step, no flag to flip, and no re-creation needed.
Is this the same as DynamoDB's eventual-consistency read option?
No. DynamoDB still lets you choose eventually consistent reads per request (at lower cost) or strongly consistent reads. S3's 2020 change removed that choice entirely — every S3 read is strongly consistent by default, with no cheaper/weaker option.
Do I still need to care about ETags?
Yes — for detecting whether an object changed (caching) and, since November 2024, for Conditional Writes using If-Match, which protect against two writers overwriting the same key. Strong consistency and conditional writes solve different problems.
Does Cross-Region Replication get the same strong consistency guarantee?
No. CRR between S3 buckets in different regions is asynchronous. The strong read-after-write guarantee applies within the region/bucket you wrote to, not to replicas in other regions.
