skip to content

In CloudFront, what does creating an invalidation for the path /index.html actually do, and how does it differ from simply letting that object's TTL expire?

level: juniorimportance: should knowfreq 58%

answer

  1. explicit purge versus waiting it out
  2. asynchronous job, not an instant switch
  3. CloudFront's caches only, not browsers
  4. wildcard counts as a single path
  5. origin content is untouched

basics

~20 s

A CloudFront invalidation asks every edge cache to drop its copy of the named paths, so the next request fetches from the origin. TTL expiry does the same thing passively, one edge at a time, when the object ages out.

solid answer

~50 s

An invalidation is an explicit, asynchronous purge request against a distribution: you submit one or more paths, CloudFront propagates the request to its edge caches, and the next viewer request for a matched path goes back to the origin instead of being served from cache. It is a job, not an instant switch — you get an invalidation id back and poll its status until it completes, typically within minutes. TTL expiry reaches the same end state without any API call: each edge independently stops serving its copy once the object's cached lifetime elapses, then refetches or revalidates on the next request. Two practical points: invalidation touches **CloudFront's** caches only, so a browser that stored the file with a long `max-age` still shows the old one; and it does nothing to the object in S3 or on your origin — the origin content is whatever you last deployed.

code

bash · 8 lines
bash
INV=$(aws cloudfront create-invalidation \
  --distribution-id E1EXAMPLE12345 \
  --paths '/index.html' '/sw.js' \
  --query 'Invalidation.Id' --output text)

aws cloudfront wait invalidation-completed \
  --distribution-id E1EXAMPLE12345 \
  --id "$INV"

go deeper

for a junior

Be able to say plainly that an invalidation is an API call that drops named paths from CloudFront's caches, and that waiting for the TTL gets you to the same place without one.

for a middle

Explain the mechanics: asynchronous propagation, path and wildcard matching, per-path billing after the free allowance, and why browser copies are untouched by it.

for a senior

Show the operating instinct — treat TTL as the routine control and invalidation as the exception, and diagnose a lingering stale copy from X-Cache and Age before firing another purge.

for a principal

Frame it as a design question: a platform where deploys need purging has content whose freshness it cannot express in URLs or headers, and fixing that removes a class of incidents rather than automating them.

## What an invalidation is An invalidation is a request you submit against a **distribution**, containing a list of **paths** and a caller reference. CloudFront returns an invalidation id and a status, propagates the purge across its edge caches, and moves the status to completed. It is asynchronous — you poll for the status rather than getting a synchronous purge. Once it completes, the affected objects are no longer served from CloudFront's caches. The next viewer request for `/index.html` is a miss and goes to the origin, which supplies the current content. ```bash aws cloudfront create-invalidation \ --distribution-id E1EXAMPLE12345 \ --paths /index.html /sw.js ``` Paths are matched against the object path as CloudFront stores it. A trailing `*` makes a wildcard: `/assets/*` matches everything beneath that prefix, and `/*` matches the whole distribution. ## What TTL expiry does instead TTL expiry needs no API call at all. Each edge cache stores an object with a computed lifetime — derived from the origin's caching headers and clamped by the cache policy's minimum, default and maximum TTL. When that lifetime elapses at a given edge, that edge stops serving the copy as fresh and goes back to the origin on the next request (often with a conditional request, so an unchanged object comes back as a small not-modified response rather than a full body). The differences that matter in an interview: | | Invalidation | TTL expiry | |---|---|---| | Trigger | explicit API call | time passing | | Scope | the paths you name, across edges | one object, per edge, as it ages | | Timing | minutes, asynchronous, pollable | exactly when the lifetime runs out | | Cost | free for a monthly allowance of paths, then per path | free | | Origin load | a burst of misses at once | spread naturally over time | ## The two things it does not do **It does not clear browser caches.** This is the single most common misunderstanding, and it is the reason "I invalidated and users still see the old page" keeps happening. If the response carried a long freshness lifetime, the viewer's own browser holds a copy and will not ask CloudFront at all until that lifetime expires. CloudFront cannot reach into a browser. The fix lives in the caching headers your origin sends for that document, not in another invalidation. **It does not change the origin.** Invalidating `/index.html` does not upload anything. If the deploy to S3 or to your application origin has not actually landed, the refetch simply pulls the old bytes again and repopulates the cache with them. Confirm the origin has the new content before blaming the CDN. ## Cost and shape As of 2025 AWS gives a monthly allowance of 1,000 invalidation paths per account at no charge and bills per path beyond that. Crucially, **a wildcard path counts as one path**, not one per matching object — so `/*` is cheap in dollars. What makes `/*` expensive is not the invoice: dumping the whole cache means every edge must refetch everything, so the origin absorbs a burst of misses and users see the slow, uncached response for a while. There are also quotas on how many invalidation requests can be in progress at once, which is why a pipeline that fires a wildcard invalidation on every commit can start queueing. ## When each is the right tool Reach for **TTL** as the default control: set the lifetime that matches how stale the content may be, and let objects roll over on their own. Reach for **invalidation** for the exception — an unversioned document that must change now, a bad deploy, content taken down for legal reasons. The production pattern is to make invalidation almost unnecessary: give assets content-hashed filenames so a new build produces new URLs that were never cached, and reserve invalidation for the small handful of stable entry-point paths such as `/index.html` or a service-worker script. ## Checking what happened CloudFront adds an `X-Cache` response header (`Hit from cloudfront` / `Miss from cloudfront`) and an `Age` header on cached responses. After an invalidation completes, the first request for the path should show a miss and an age near zero. If it still shows a hit with a large age, either the invalidation has not finished, the path you submitted does not match the stored object path, or you are looking at a response your own browser cached.

  • You invalidated /app.js, the invalidation completed, and a user still sees the old bundle. What are the two most likely causes?
    Either the user's own browser is serving a copy it stored with a long freshness lifetime — invalidation never reaches browser caches — or the new bundle was never actually published to the origin, so the refetch pulled the same old bytes. Check with a fresh private-window request and by fetching the object directly from the origin.
  • Does invalidating /assets/* cost more than invalidating a single file?
    No. A wildcard path is billed as one path regardless of how many objects it matches, and the first 1,000 paths a month are free as of 2025. The real cost is operational: every matched object must be refetched, so the origin takes a burst of misses and viewers see uncached latency.

saying these in an interview costs you the question

  • Thinks an invalidation also clears users' browser caches
  • Believes invalidation deletes the object from S3 or the origin
  • Expects the purge to take effect instantly and globally
  • Assumes /* costs one charge per matching file
  • Treats invalidation as the normal way to deploy static assets

context