
Backend Engineering Interview Concepts for Production Part 4:Caching and Cache Invalidation on AWS
Learn how caching speeds up a Black Friday sneaker sale, and why cached prices can become outdated. Understand cache hits, misses, TTLs, and cache invalidation on AWS with Amazon ElastiCache and DynamoDB. Also build a project on AWS which implements this so you can learn hands-on with help of AWS Agent Toolkit. Interview questions shown at the end of the article.
Series: Backend Interview Concepts to Know for Production (4 articles)
- …
- 4Backend Engineering Interview Concepts for Production Part 4:Caching and Cache Invalidation on AWS This article
Caching stores a reusable copy of data in a faster location so an application can avoid repeating work. Cache invalidation removes or replaces that copy when the source data changes.
A cache can reduce response time and database load, but it creates a new question: how stale can the copy become before it causes harm?
This beginner-friendly guide follows one product through cache hits, misses, price changes, expiration, and traffic spikes. Then it maps those ideas to Amazon ElastiCache Serverless for Valkey, AWS Lambda, Amazon API Gateway, and Amazon DynamoDB.
This is Part 4 of Backend Engineering Interview Concepts for Production.
Why cache repeated database reads?
Imagine an online store selling Cloud Nine sneakers for $89. Their product ID is 42.
A creator posts the shoes, and thousands of shoppers open the same product page. The application keeps asking the database for the same name, description, image links, and browsing price.
For a small store, reading the database on every request may be fine. There is no prize for adding a cache before you need one. Under heavy traffic, though, repeated reads consume database capacity and can make requests wait behind other work.
Indexes still matter because they help the database find data on a cache miss. A cache goes one step further: when a reusable answer exists, the application can skip the database read.
Consider this illustrative example, not an AWS benchmark. Suppose an expensive product lookup and response assembly takes 300 milliseconds, while the rest of the request takes 200 milliseconds.
Work for one product view | Without a cache | With a cache hit |
|---|---|---|
Retrieve or assemble product data | 300 ms | 5 ms from the cache |
Other request work | 200 ms | 200 ms |
Total in this simplified example | 500 ms | 205 ms |
The hit saves 295 milliseconds, reducing the example's total time by 59%. It does not make the whole request finish in 5 milliseconds. The network and the rest of the application have not retired.
These figures represent a hypothetical workload that assembles data from several places. They are not typical DynamoDB single-item read times. Measure your application before predicting an improvement.
If 90% of 1,000 product requests per second hit the cache, only 100 requests need the source lookup, assuming one source read per miss and no extra refresh work. That avoids 900 repeated source reads each second.

The workload and timings are teaching examples. Fewer database reads do not imply the same percentage reduction in the total bill.
A cache also costs money and adds another system to operate. Add one when measurements show that reused data, source load, or response time makes the trade-off worthwhile.
How does cache-aside work?
Cache-aside, also called lazy loading, is a common caching pattern. The application checks the cache first and loads data into it after a miss.
Alice opens product 42:
- The application checks the cache.
- No entry exists, so this request has a cache miss.
- The application reads the product from the database.
- It stores a copy in the cache with an expiration time.
- It returns the product to Alice.
Bob opens the same page a moment later. The application finds the copy. This is a cache hit, so it returns the cached product without reading the database.

On a miss, the application checks the cache and reads the source. A miss can take longer than a request that never checked a cache.
The logic looks like this. It is explanatory pseudocode rather than a complete production implementation:
key = "product:v1:42:USD:US"
product = cache.get(key)if product exists: return product # cache hit
product = database.readProduct(42) # cache misscache.set(key, product, ttl=60 seconds)return productThe string
product:v1:42:USD:US is the cache key, the label used to store and retrieve this answer. It includes:-
42for the product
-
USDfor the currency
-
USfor the market
-
v1for the cached data format
The key must include every value that changes the answer. A US price must not leak into a different market, and one customer's private response must not become another customer's cache hit.
The database remains the source of truth, which means it holds the authoritative product record. The cache holds a replaceable copy. Losing the copy should cause extra work, not the permanent disappearance of the shoe business.
What is cache invalidation?
Cache invalidation removes or marks a cached value unusable after its source data changes. Without it, a cache can return an old answer even though the database contains the new one.
Suppose an administrator changes the Cloud Nine price from $89 to $99. The database update succeeds, but the cache still holds $89. A shopper reloads the page, gets a cache hit, and sees the old price.
The application is now fast at being wrong.
That $89 copy is stale data. A cache hit proves that the application found an entry. It does not prove that the entry is current.
A practical cache-aside write policy is:
- Update the database and wait for the write to succeed.
- Delete the affected cache entry.
- Let the next read miss, fetch the new value, and refill the cache.
Deleting the cached response keeps the write path smaller than rebuilding every cached form of the product. The next reader pays for a miss.
Updating the database first also avoids one obvious race. If the application deleted the cache first, another request could refill it with $89 before the database saved $99.

Invalidation removes the old copy after a change. Expiration removes it when its allowed lifetime ends. Neither action fetches a replacement by itself.
Database first, then delete is a useful starting policy. It is not an atomic transaction across the database and cache, so failure and concurrency cases remain.
What is a TTL, and does it refresh the cache?
TTL means time to live. It controls how long a cached entry may remain available before it expires.
If the application caches product 42 for 60 seconds at 10:00:00, a lookup after 10:01:00 should not return that entry. With cache-aside, the next request misses, reads the database, and stores a new copy.
Expiration does not refresh data. If nobody requests the product after expiry, the cache stays empty. Scheduled or background refresh requires more application logic. Valkey's expiration documentation describes expiration as removing a key, not loading its replacement.
A TTL helps when invalidation fails or another writer skips the invalidation policy. It gives the stale copy an eventual exit. It does not guarantee fresh data because a delayed source read can still store an older result.
Choose a TTL by asking how old this answer may become before it harms the user:
Information | Risk from stale data | Caching direction |
|---|---|---|
Product description or image link | A shopper sees an older description or photo | Often cacheable with a TTL and write invalidation |
Browsing price | A shopper sees an outdated offer | Use a short, deliberate freshness policy and validate before purchase |
Checkout price and stock reservation | The store charges the wrong amount or oversells | Use the authoritative transaction or conditional-write path |
Private order details | A shopper sees stale status or another customer's data | Cache only with strict authorization, key isolation, and freshness rules |
There is no universal five-minute TTL. Five minutes may suit a product description and be unacceptable for a revoked permission.
Which cache write strategy should you use?
Cache-aside defines the read path. The application also needs a policy for writes and freshness.
Approach | Mechanism | Benefit | Trade-off |
|---|---|---|---|
Cache-aside with invalidation | Save the database change, then delete the cached copy | Simple; only requested data occupies the cache | A failed deletion or concurrent reader can leave stale data |
Application dual write | Save the database, then update the cache | Readers may find the new value in the cache | Two separate writes can fail or finish out of order |
Write-through cache | Send the write through a cache service that updates the source and its cache | The service coordinates its supported write path | You depend on that service's API and consistency rules |
Write-behind | Accept the write in a cache or buffer, then save it to the database later | Can reduce acknowledgement latency and batch source work | Unsaved changes need durable buffering and recovery |
TTL expiration | Let the cached copy expire and reload it on demand | Adds a time limit to each entry | Stale data remains before expiry; expiry can cause a burst of misses |
Event-based invalidation | Process a change event and delete affected keys | Covers changes made by several writers | Events can arrive late, repeat, or fail |
These approaches can work together. The sneaker store can use cache-aside reads, database-first invalidation, and a TTL on every cached product.
Product 42 may appear in a detail entry, category results, and a popular-products list. A price change must invalidate every cached answer that contains that price. This dependency problem is one reason to keep cache entries small and naming rules predictable.
Can database-first invalidation still return stale data?
Yes. Separate database and cache operations leave a race window.
Consider this order of events:
- A shopper misses the cache and reads the old $89 product, then pauses before filling the cache.
- An administrator saves $99 in the database and deletes the cache key.
- The paused shopper resumes and stores the old $89 result in the cache.
The database update and cache deletion both succeeded. The delayed reader put the old value back afterward.

Separate database and cache actions leave a window in which a delayed reader can reintroduce an older value.
A short TTL plus invalidation may be enough for a browsing page that tolerates brief staleness. Stricter requirements may need version checks, coordinated writes, or no cache on that decision path.
Putting a version in the key does not solve the race by itself. Readers still need a reliable way to discover the current version, and delayed work must not replace newer data.
What is a cache stampede?
A cache stampede happens when many requests miss the same key and try to rebuild it at the same time. It can send a sudden burst of traffic to the database when the cache stops protecting it.
Suppose product 42 expires while 200 shoppers request it. All 200 see a miss. Without coordination, all 200 read the database.
Several controls address different parts of this problem:
- Request coalescing: one request fetches the product while the others wait for its result. An in-process lock coordinates one process. Multiple Lambda execution environments need shared coordination.
- A short lease: one worker receives temporary permission to rebuild a key. The lease needs an owner token, an expiry, bounded waits, and a fallback for failure.
- TTL jitter: the application varies expiration times across keys so many products do not expire in the same second. Jitter does not stop many requests from missing one hot key.
- Stale-while-revalidate: the application serves an acceptable older copy for a short grace period while one worker refreshes it.

Coalescing reduces duplicate work for one key. Jitter spreads expiration across different keys.
A hot key is a key that receives heavy traffic. Product 42 has become famous, which is good for sales and less charming for the database.
Stale-while-revalidate only suits data for which an older answer remains safe. Do not approve a purchase using yesterday's inventory because the cache looked confident.
How do negative caching and eviction work?
Negative caching remembers a confirmed absence
Suppose thousands of requests ask for product 999, which does not exist. Rechecking the database on every request wastes work.
Negative caching stores a short-lived "not found" result. Use an explicit marker so the application can distinguish a cached absence from a cache miss. If product 999 gets created, invalidate that marker.
A timeout is not a confirmed absence. Do not turn "the database did not answer" into "the product does not exist." Limit untrusted keys as well, or random fake IDs can fill the cache.
Eviction removes entries to make room
A cache has finite capacity. Its eviction policy decides which entries to remove when it needs space.
Common engine policies include LRU, which favors entries used in the recent past, and LFU, which favors entries used many times. Valkey uses approximations for these policies. Available controls differ between node-based ElastiCache and ElastiCache Serverless, so check the options for the cache type you choose. The Valkey eviction guide explains engine behavior.
Expiration removes an entry because its lifetime ended. Eviction removes one because the cache needs room. Your application must handle a miss in either case.
When should you cache data?
Caching fits data that receives repeated reads, costs enough to retrieve or compute, and has an acceptable freshness window.
Good candidates include popular product details and expensive summaries that many users request. Measure first. A cache will not repair a slow payment provider, an oversized image, or unnecessary browser work.
Avoid or delay caching when:
- The source read already meets the latency and capacity target.
- Most requests ask for different data, which produces a low hit rate.
- Data changes as often as users reuse it.
- The decision needs current authoritative state, such as stock reservation or permission checks.
- The team cannot define safe behavior for cache failure or stale data.
A cache outage also changes source traffic. "Read the database instead" can overload a database sized for a 90% cache hit rate. Use short cache timeouts, controlled fallback, request limits, and enough source capacity for the failures you promise to support. The Amazon Builders' Library caching guide covers this dependency in more detail.
Which caching metrics should you monitor?
Monitor speed, source protection, and freshness together:
Signal | Question it answers |
|---|---|
Cache hit rate | Do lookups find cached entries? |
End-to-end p95 latency | How long do 95% of requests take? |
Database reads, load, and throttling | Does the cache reduce source pressure? |
Cache errors, timeouts, and evictions | Is the cache healthy and large enough? |
Invalidation failures and stream delay | Do source changes reach the cache policy? |
Cached age or version checks | Does the cache meet the freshness target? |
Hit rate alone cannot tell you whether a cached value is correct. A perfect hit rate for the wrong price is not a success metric.
How does caching work on AWS?
For a learning project, use Amazon DynamoDB as the authoritative product store and Amazon ElastiCache Serverless for Valkey as the shared cache. Put the cache-aside code in AWS Lambda behind Amazon API Gateway.
Valkey is an in-memory key-value data store. ElastiCache Serverless removes the need to choose and maintain cache nodes and manages capacity for the cache. Your application still owns its keys, TTLs, invalidation, fallback, and freshness rules. See the Amazon ElastiCache overview .
DynamoDB already provides low-latency access at scale. Another cache only helps when the workload reuses data enough to justify the added service. This example uses Valkey to make general cache-aside decisions visible.

This is a conceptual learning architecture rather than a complete production network diagram. The Agent Toolkit is a development companion; customer traffic does not pass through it. You can set up AWS Agent toolkit by following instructions in this guide here .
AWS service | Role in the example |
|---|---|
Amazon API Gateway | Receives product API requests and invokes Lambda. An authorizer protects the admin update route. |
AWS Lambda | Checks the cache, reads DynamoDB after a miss, fills the cache, and invalidates entries after product edits. |
Amazon ElastiCache Serverless for Valkey | Stores shared product copies that Lambda execution environments can reuse. |
Amazon DynamoDB | Stores the authoritative product records. |
Amazon CloudWatch | Stores logs and metrics for latency, hits, misses, invalidation failures, and fallback. |
Follow one AWS request
Alice requests product 42. API Gateway invokes Lambda. Lambda looks for
product:v1:42:USD:US in ElastiCache.A hit returns the cached product. A miss reads DynamoDB, stores the result in ElastiCache with a TTL, and returns it. The next shopper can reuse that entry.
An authorized admin price update writes DynamoDB first and then deletes affected cache keys. The application records invalidation failures because a DynamoDB write can succeed even if cache deletion fails.
For this single-Region learning example, a strongly consistent
GetItem against the base DynamoDB table can avoid a refill from an eventually consistent read that has not caught up with a completed write. DynamoDB tables and local secondary indexes support strong reads; global secondary indexes and Streams do not. Strong reads also cost more read capacity than eventual reads. See DynamoDB read consistency .A strong read does not prevent the delayed-reader race described earlier. Checkout still needs an authoritative transaction or conditional write.
Secure the cache and network path
Lambda needs VPC access to connect to ElastiCache. A virtual private cloud (VPC) is an isolated network you control in AWS. Use private subnets and security groups that allow cache traffic only from the Lambda security group.
ElastiCache Serverless encrypts data at rest and in transit. TLS protects the connection, but encryption alone does not decide which client may run commands. Attach an ElastiCache user group, grant only the commands and key patterns the application needs, and authenticate with a password or IAM authentication. AWS documents these layers in ElastiCache security and IAM authentication .
A DynamoDB gateway VPC endpoint adds a DynamoDB route to the route tables associated with the Lambda subnets. DynamoDB traffic can stay on the AWS network without a NAT gateway or internet gateway, and AWS does not charge an additional gateway endpoint fee. The endpoint does not provide access to other internet services. Use an endpoint policy plus the Lambda execution role to limit table access. See gateway endpoints for DynamoDB .
Create SDK clients and cache connections outside the Lambda handler so a warm execution environment can reuse them. The application must still reconnect after an idle closure or failover. Use bounded timeouts and limit concurrency so a cache problem cannot consume every Lambda invocation or overwhelm DynamoDB. See Lambda best practices .
Send cache metrics through Lambda logs
Lambda publishes standard service metrics such as invocations, duration, and errors. Cache hits, misses, stale-value checks, and invalidation failures are application-specific, so your code must emit them.
One beginner-friendly option is CloudWatch Embedded Metric Format (EMF). The function writes structured metrics to its logs, and CloudWatch extracts them. This avoids a direct
PutMetricData call from the VPC-attached function. The execution role still needs CloudWatch Logs permissions, and custom metrics and log ingestion incur charges. Avoid high-cardinality metric dimensions such as product IDs or request IDs. See CloudWatch Embedded Metric Format .Invalidate changes from other writers
If every product update uses one Lambda handler, that handler can invalidate the cache. Imports, admin tools, or other services can break that assumption.
DynamoDB Streams can capture table changes. A Lambda event source mapping reads those records and invokes a function that deletes affected cache keys. This moves invalidation closer to the database change, but it remains asynchronous.
Lambda manages polling and retries for the event source mapping. Configure retry limits, maximum record age, partial batch failure handling, and an on-failure destination. Make deletion idempotent because records may be processed more than once. Monitor Lambda errors and
IteratorAge , a metric that shows how far stream processing has fallen behind, so delayed invalidation does not hide in the background. See DynamoDB Streams with Lambda .Deleting the same cache key twice is safe for this design. Writing old values back into the cache needs ordering or version protection.
Where do CloudFront, RDS, Aurora, and DAX fit?
You do not need all of these services in one project.
Amazon CloudFront caches HTTP content at edge locations. It can deliver product images and other cacheable files closer to users. Use versioned file names when content changes, and remember that CloudFront, browser, and application-data caches have separate lifetimes. See CloudFront invalidation guidance .
Amazon RDS or Amazon Aurora can replace DynamoDB as the authoritative database in an application-managed cache-aside design. Cache hits reduce repeated queries, but sound indexes and query design still matter after a miss.
Amazon DynamoDB Accelerator (DAX) is a caching service built for DynamoDB API access. DAX caches supported eventually consistent reads. It sends strongly consistent reads to DynamoDB without caching the results. Writes sent through DAX use write-through behavior for its item cache, while its item cache and query cache have separate consistency rules. Read how DAX works and DAX consistency before choosing it.
ElastiCache is a better teaching fit here because the application defines the keys, stored values, and invalidation policy.
Account for the full AWS cost
ElastiCache Serverless bills stored data in GB-hours and requests in ElastiCache Processing Units (ECPUs). A minimum amount of data storage is metered per cache. API Gateway, Lambda, DynamoDB, CloudWatch, and paid network resources can add charges. Check current ElastiCache pricing for your Region rather than copying a dollar figure into an architecture plan.
Build a small caching project
A beginner project only needs a product list, a product detail endpoint, and a protected price-update endpoint. Add a development label to each response: HIT, MISS, or SOURCE FALLBACK, plus request duration and cached age.
Try these experiments in a test environment:
- Request a product twice and compare the miss with the hit.
- Change $89 to $99, then watch invalidation and refill.
- Disable invalidation, observe the stale price, and wait for expiry.
- Send a small bounded burst after expiry, then compare duplicate source reads with coalescing enabled.
- Simulate a cache timeout and confirm that fallback limits protect DynamoDB.
These tests expose the mechanics. They do not prove production capacity; that requires representative load and failure testing.
The Agent Toolkit for AWS can help a coding agent consult AWS documentation, draft infrastructure, and review a deployment plan. Its AWS MCP Server exposes AWS tools over MCP, while the toolkit also includes skills and agent integrations. Installing it does not grant account access; AWS operations still depend on the credentials and IAM permissions you provide. The toolkit has no additional charge, while AWS resources it creates or accesses use normal service pricing. Check the current quick start before installation because setup requirements can change.
Common caching interview questions and answers
A strong interview answer follows a useful order: name the failure you want to prevent, explain the mechanism, then end with the trade-off and the production signals you would watch.
How would you keep cache and database reasonably consistent?
Failure to prevent: I want to avoid serving stale data after a successful database change. Mechanism: I would keep the database as the source of truth, update it first, then invalidate every affected cache key. Cache entries would also have TTLs as a backup. If several systems can write the data, I would process database change events and make invalidation idempotent. Critical decisions such as checkout would bypass the browsing cache or validate against the authoritative path. Trade-off and observation: The database and cache are separate systems, so brief staleness and race windows remain. I would monitor invalidation failures, event delay, cached age or version, source comparisons, and stale-data incidents.
What is a cache stampede and how would you prevent it?
Failure to prevent: I want to stop many requests from rebuilding the same expired value and overwhelming the database. Mechanism: I would coalesce requests so one worker refreshes a hot key while others wait for its result. Across application instances, I would use a short shared lease with ownership, expiry, bounded waits, and a failure fallback. TTL jitter spreads expiry across different keys, and stale-while-revalidate can help when an older value remains safe. Trade-off and observation: Coordination adds latency and failure modes, while serving stale data is not safe for every path. I would monitor misses per key group, concurrent source reads, database throttling, refresh time, lease timeouts, and p95 latency during expiry.
When should you not cache data?
Failure to prevent: I want to avoid added cost, stale decisions, and a cache that provides little benefit. Mechanism: I would skip the cache when source reads already meet the target, requests have little reuse, data changes faster than it is read, or the decision requires current authoritative state. I would fix the real bottleneck first if it sits in images, frontend code, or another service. Trade-off and observation: Going without a cache sends more reads to the source but keeps the design smaller and removes cache-consistency work. I would watch source latency, capacity, cost, read reuse, and error rates, then add caching only if measurements support it.
A concise caching answer for interviews
Repeated product reads can increase latency and database load, so I would consider cache-aside for reusable browsing data. A hit returns the copy; a miss reads the source and fills the cache. I would update the database first, invalidate affected keys, and use TTLs as a backup. I would account for delayed readers, stampedes, eviction, and cache outages. Checkout would use authoritative validation rather than trusting the browsing cache. In production, I would compare end-to-end latency, source load, hit rate, invalidation failures, and freshness against the extra cost and complexity.
Caching can stop the store from asking the database the same question a thousand times per second. Invalidation makes sure it notices when the answer changes.
Next in the series: retries, backoff, and jitter. Sometimes "try again" recovers a request. Sometimes it gives an overloaded dependency more work.
Series: Backend Interview Concepts to Know for Production (4 articles)
- …
- 4Backend Engineering Interview Concepts for Production Part 4:Caching and Cache Invalidation on AWS This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article