DSA
System design test
“This is the test feature”
Why Caching Makes Modern Applications Faster
Every time a user opens an application, the system may need to fetch data from a database, call another service, perform calculations, and finally return a response.
That works well at small scale.
But when thousands or millions of requests start hitting the same system, repeatedly performing the same expensive work becomes wasteful.
This is where caching becomes useful.
Caching means keeping frequently used data in a faster temporary storage layer so the application does not need to calculate or fetch the same information again and again.
A Simple Example
Imagine an e-commerce application showing a product page.
Without caching, every request might perform a database query:
SELECT
id,
name,
description,
price,
inventory
FROM products
WHERE id = 101;
If 10,000 users request the same popular product, the database could execute essentially the same query thousands of times.
Instead, we can store the result temporarily in a cache such as Redis.
The flow becomes:
The database is now contacted only when the requested information is not already available in the cache.
Cache Hit vs Cache Miss
Two terms appear constantly when discussing caching.
Cache Hit
A cache hit means the requested data already exists in the cache.
Request
↓
Cache
↓
Data Found
↓
Return Response
This is normally very fast.
Cache Miss
A cache miss means the requested data is not available.
Request
↓
Cache
↓
Not Found
↓
Database
↓
Store Result in Cache
↓
Return Response
Cache misses are more expensive, but the result can be cached so future requests become faster.
A good caching system tries to maximize useful cache hits while ensuring users do not receive incorrect or excessively stale data.
Basic Redis Example
A simplified Node.js example might look like this:
async function getProduct(productId: string) {
const cacheKey = `product:${productId}`;
const cachedProduct = await redis.get(cacheKey);
if (cachedProduct) {
return JSON.parse(cachedProduct);
}
const product = await database.products.findById(productId);
await redis.set(
cacheKey,
JSON.stringify(product),
{
EX: 300,
}
);
return product;
}
Here the cached value expires after 300 seconds, or five minutes.
The first request may reach the database.
Requests after that can usually return directly from Redis until the cached value expires.
Why Redis Is Commonly Used
Redis stores data primarily in memory, which makes reads and writes extremely fast compared with many disk-backed database operations.
It also supports useful data structures such as:
- Strings
- Hashes
- Lists
- Sets
- Sorted sets
- Streams
Caching is only one of many Redis use cases.
Applications also use Redis for:
- Sessions
- Rate limiting
- Queues
- Counters
- Leaderboards
- Distributed locks
- Temporary application state
Common Caching Strategies
There is no single strategy that works for every application.
1. Cache Aside
The application checks the cache first.
If data is missing, it queries the database and stores the result.
This is one of the most common caching patterns.
2. Write Through
Whenever data changes, the application writes to the cache and underlying database as part of the same workflow.
This can keep cached data fresher but increases write complexity.
3. Write Behind
Data may be written to the cache first and persisted to the database asynchronously.
This can improve write performance, but the architecture becomes more complicated because failures must be handled carefully.
Comparing the Strategies
| Strategy | Read Performance | Write Complexity | Main Advantage |
|---|---|---|---|
| Cache Aside | Fast | Low | Simple and widely used |
| Write Through | Fast | Medium | Cache remains synchronized |
| Write Behind | Fast | High | Very fast writes |
The right strategy depends on the application's consistency and performance requirements.
The Hard Problem: Cache Invalidation
Caching sounds simple until the original data changes.
Suppose the cached product price is:
₹999
An administrator updates the database:
₹799
But Redis still contains:
₹999
Users could temporarily see outdated information.
This is called stale data.
A system therefore needs an invalidation strategy.
Common approaches include:
- Delete the cache entry when the underlying data changes.
- Update the cache immediately.
- Use a short expiration time.
- Use events to invalidate caches across services.
For example:
async function updateProductPrice(
productId: string,
newPrice: number
) {
await database.products.update(productId, {
price: newPrice,
});
await redis.del(`product:${productId}`);
}
The next request becomes a cache miss, retrieves the new database value, and repopulates the cache.
Caching incorrect data faster does not make a system better. Correctness should come before cache-hit percentage.
What Should You Cache?
Good candidates usually have one or more of these properties:
- Frequently requested
- Expensive to calculate
- Expensive to fetch
- Changes relatively infrequently
- Safe to serve temporarily from slightly older data
Examples include:
- Product information
- Public profiles
- Configuration
- Popular search results
- Homepage content
- Aggregated analytics
Not every value should be cached.
Highly sensitive or rapidly changing information may require much stricter consistency.
Cache Keys Matter
Cache keys should be predictable and clearly namespaced.
Good:
product:101
user:842:profile
course:92:metadata
Less useful:
101
842
data1
Namespaced keys reduce collisions and make debugging easier.
A real application may also include versions:
product:v2:101
Changing the version allows an application to effectively invalidate an entire family of old cache entries.
Another Important Problem: Cache Stampede
Imagine a popular cached value expires.
At that exact moment, thousands of requests arrive.
All of them see a cache miss and all of them query the database.
This is known as a cache stampede.
Common solutions include:
- Distributed locks
- Request coalescing
- Staggered expiration times
- Refreshing popular values before expiration
- Serving stale data briefly while refreshing in the background
Caching Is a Trade-Off
Caching gives us speed, but that speed introduces another copy of our data.
Now the system must think about:
- Expiration
- Invalidation
- Memory limits
- Eviction
- Consistency
- Failures
- Cold starts
A cache should therefore solve an actual performance problem rather than being added automatically to every feature.

Final Mental Model
Think of caching like keeping frequently used books on your desk.
Without a cache:
Need information
→ Walk to library
→ Find book
→ Read information
→ Return
With a cache:
Need information
→ Check desk
→ Found it
→ Continue working
The desk is much faster.
But when the original book changes, you need a reliable way to make sure the copy on your desk does not remain outdated forever.
That is essentially the challenge of caching in distributed systems.
Key Takeaways
- Caching avoids repeating expensive work.
- A cache hit is significantly cheaper than repeatedly querying the primary database.
- Redis is commonly used because it provides extremely fast in-memory access.
- Cache Aside is a practical starting strategy.
- Expiration alone is not always enough; invalidation matters.
- Cache stampedes can overload the database.
- Cache keys should be structured and predictable.
- Caching improves performance, but introduces consistency trade-offs.
Caching is not simply about making applications faster.
It is about deciding which work should not need to happen twice.
Finished this article?
Mark it done to update your series progress.