You Don’t Need to Memorize System Design. You Need to Understand These 6 Ideas.
The moment I stopped memorizing Redis, Kafka, CAP, SQL, and other buzzwords, system design started making much more sense.

There was a time when I could explain what a CDN was.
- I knew what Redis did.
- I had memorized the CAP theorem.
- I could even explain the difference between SQL and NoSQL.
But then someone would ask me:
“Okay, design this system.”
And suddenly, all those definitions became almost useless.
- I knew the words.
- I just didn’t know why I was choosing them.
That is one of the biggest differences between knowing system design concepts and actually being able to design a system.
A senior engineer isn’t simply someone who knows more technologies.
A senior engineer can look at a problem and ask:
Where is the bottleneck?
What can fail?
What needs to be consistent?
What can be delayed?
Where should the state live?
Once you start thinking this way, system design becomes much less about memorizing components and much more about making decisions.
Here are six concepts that changed the way I think about system design.
1. Statelessness: The Secret Behind Horizontal Scaling
Let’s start with something that sounds extremely simple:
Stateless servers.
Most people learn horizontal scaling like this:
1 Server
↓
2 Servers
↓
10 Servers
↓
100 ServersJust keep adding machines.
But there is a problem.
Can every server actually handle every request?
Suppose you have three application servers:
Load Balancer
/ | \
/ | \
Server A Server B Server CA user logs in through Server A.
Server A stores:
session = {
userId: 123,
loggedIn: true
}Now the user’s next request goes to Server B.
Server B has no idea who this user is.
Why?
Because the user’s state was stored inside Server A.
You have accidentally created a dependency.
The load balancer can no longer freely send requests anywhere.
You might solve this using sticky sessions, where the user is always sent back to Server A.
But now imagine Server A crashes.
The user’s session disappears with it.
And your supposedly scalable architecture has a single-server dependency.
A better approach
Move the shared state somewhere all servers can access.
For example:
Load Balancer
/ | \
/ | \
Server A Server B Server C
\ | /
\ | /
RedisNow Server A, B, and C can all access the same session data.
The application servers don’t need to remember the user.
Every request carries the information needed to process it, often through a token.
This makes the servers stateless.
And suddenly:
- You can add more servers.
- You can remove servers.
- A server can crash without losing the user’s session.
- Requests can move freely between servers.
So the next time someone asks:
“How would you scale this application?”
Don’t immediately say:
“I’ll add more servers.”
First ask:
“What state is currently stored inside each server?”
That question is often more important.
2. Caching Is Really a Speed vs Freshness Trade-off
Caching is everywhere.
- Your browser has a cache.
- Your CDN has a cache.
- Your application might use Redis.
- Your database might have caching mechanisms too.
At first, this can feel like four completely different topics.
But there is a simple idea behind all of them:
Caching means keeping a copy of data somewhere faster, while accepting that the copy may not always be the latest version.
Think about a food delivery app.
Suppose 100,000 people are requesting the same restaurant menu.
Without caching:
100,000 users
↓
Application
↓
DatabaseThe database gets hammered with the same request again and again.
Instead:
100,000 users
↓
Application
↓
Redis
↓
DatabaseThe first request may fetch the data from the database.
The application stores it in Redis.
The next 99,999 requests can potentially get the data from Redis much faster.
But now we have another question.
Q. What happens when the restaurant changes its menu?
The cached copy might still contain the old menu.
That’s the fundamental caching trade-off:
More caching
↓
Faster responses
↓
Potentially older dataThis is why questions like these matter:
- How fresh does this data need to be?
- How often does it change?
- Where is the bottleneck?
- How expensive is the original request?
For example, a profile picture can usually be cached aggressively.
A bank balance is a very different story.
You probably don’t want to show:
₹50,000
when the real balance is:
₹5,000.
Some caching concepts worth knowing
You don’t need to memorize every caching technology.
Start with these:
TTL — Time To Live
How long should cached data remain valid?
Cache entry
↓
10 minutes
↓
Expires
↓
Fetch fresh dataCache-aside
The application checks the cache first.
Request
↓
Cache?
/ \
Yes No
| |
Data DB
↓
CacheWrite-through / write-back
These are different strategies for deciding when changes should be written to the underlying data store.
The important thing isn’t remembering a definition.
The important question is:
How stale can my application afford to be?
3. CAP Theorem: Stop Saying “Pick Any Two”
CAP theorem is one of those topics that many developers memorize for interviews.
The classic explanation is:
Consistency + Availability + Partition Tolerance
You can only have two.
But that explanation can hide the more important part.
In a distributed system, network failures happen.
- Servers can lose connectivity.
- Packets can be dropped.
- Networks can partition.
So partition tolerance isn’t really something you casually turn off in a real distributed system.
The more useful question becomes:
When a network partition happens, do I prefer consistency or availability for this particular operation?
Let’s say you update your profile name.
You might want every subsequent read to immediately see the new value.
That’s strong consistency.
But imagine a social media feed.
If your friend’s new post takes a few seconds to appear, is the entire application broken?
Probably not.
That’s where eventual consistency can be perfectly reasonable.
And here’s the important part:
The same system can use both.
For example:

So don’t answer a system design question with:
“I’ll use eventual consistency.”
That’s incomplete.
Instead ask:
Which operation needs strong consistency, and which operation can tolerate stale data?
That is much closer to real system design.
4. Message Queues: Don’t Make Everything Happen at Once
Imagine an e-commerce application.
A customer clicks:
Place Order
Your backend now needs to:
Place Order
↓
Check Inventory
↓
Process Payment
↓
Send Email
↓
Send Notification
↓
Return ResponseLooks straightforward.
But there is a problem.
- What if the notification service is down?
- Does the entire order fail?
- What if sending the email takes 5 seconds?
- Does the customer have to wait 5 seconds just because an email needs to be sent?
This creates a chain of dependencies.
A message queue changes the architecture.
Instead:
Order Service
|
↓
Message Queue
/ | \
↓ ↓ ↓
Inventory Payment NotificationThe order service can publish an event:
OrderPlaced- A queue such as Kafka or SQS can hold that event.
- Different services consume it independently.
- Now suppose the notification service goes down.
- The message doesn’t necessarily disappear.
- It can wait in the queue until the service is available again.
That’s a major reliability improvement.
The bigger idea here is:
Don’t make unrelated services depend on each other synchronously unless they actually need to.
Ask yourself:
Does this operation need to happen before I respond to the user?
If the answer is no, a queue might be useful.
For example:
- Sending an email → usually doesn’t need to block the order.
- Generating an analytics event → usually doesn’t need to block the request.
- Updating a search index → might be asynchronous.
But charging a customer’s card?
That’s a different question.
You need to carefully define what must happen before the order can be considered successful.
5. SQL vs NoSQL Isn’t “Old vs New”
This is probably one of the most misunderstood system design debates.
People sometimes talk about it like this:
SQL = traditional
NoSQL = modernThat’s not the right way to think about it.
The better question is:
What guarantees does my application need from the database?
SQL databases are strongly associated with ACID transactions.
ACID stands for:
Atomicity
A transaction is all-or-nothing.
Imagine transferring ₹1,000:
Account A: -₹1,000
Account B: +₹1,000You don’t want this:
A: -₹1,000
B: ₹0If one part fails, the transaction should not leave your data in a broken state.
Consistency
The database moves from one valid state to another.
Your constraints and rules continue to hold.
For example, if your database says:
age >= 18you don’t want a transaction silently creating:
age = 12when that violates your data rules.
Isolation
Multiple transactions should not interfere with each other in unpredictable ways.
Imagine two users trying to buy the last available iPhone at exactly the same time.
Your database needs to handle those concurrent operations correctly.
Durability
Once the database confirms:
“Transaction committed.”
the data should survive a crash.
So when does NoSQL make sense?
NoSQL databases can be useful when you need things like:
- Flexible data models
- Large-scale horizontal distribution
- High write/read throughput
- Workloads where strict relational guarantees aren’t required everywhere
Think about an activity feed.
If a new like takes a second to appear, the system may be perfectly fine.
Compare that with transferring money.
A temporary inconsistency there could be catastrophic.
This is why the real question isn’t:
SQL or NoSQL?
It’s:
What guarantees does this particular piece of data require?
And in real production systems, you don’t necessarily have to choose one database for everything.
You can use different databases for different workloads.
6. API Design: Your API Is a Contract
This one is easy to underestimate.
You might think an API is simply:
GET /users
POST /orders
GET /productsBut once other applications start using your API, something changes.
Your API becomes a contract.
Imagine your mobile application depends on:
{
"userId": 123,
"name": "Neha"
}Then one day you change it to:
{
"id": 123,
"fullName": "Neha"
}Your backend code might still work perfectly.
But your mobile application could break.
That’s why changing a public API isn’t always just a code change.
It can become a migration problem involving every client using that API.
REST vs GraphQL
Again, don’t think:
REST is better.
or:
GraphQL is better.
They optimize for different things.
REST
REST is often simple, predictable, and cache-friendly.
For example:
GET /users/123
GET /users/123/orders
GET /products/456It can work very well for public APIs and clients that benefit from straightforward endpoints.
GraphQL
GraphQL allows the client to ask for exactly the data it needs.
For example:
query {
user(id: 123) {
name
email
orders {
id
total
}
}
}This can be useful when different clients have very different data requirements.
But regardless of whether you choose REST or GraphQL, some principles remain important:
- Think carefully about versioning.
- Keep your contract explicit.
- Design around resources rather than internal implementation details.
- Document the API.
- Think about backward compatibility.
Because once people depend on your API, your implementation can change, but your contract cannot casually change with it.
The Bigger Lesson: Stop Memorizing System Design
There is a pattern connecting all six concepts.
Look at them again:
Statelessness
↓
Where should state live?
Caching
↓
How much freshness can I sacrifice for speed?
CAP
↓
Which operations need consistency?
Message Queues
↓
Which work needs to happen synchronously?
Databases
↓
Which data needs strong guarantees?
API Design
↓
What contract can my clients depend on?Notice something?
None of these questions starts with:
“Which technology should I use?”
That’s intentional.
A beginner often starts with the technology:
“Should I use Redis?”
A more experienced engineer starts with the problem:
“Why is this request slow?”
Then they might discover that caching is useful.
The beginner says:
“I’ll add Kafka.”
The experienced engineer asks:
“Does this operation really need to be synchronous?”
Then a queue might become the answer.
That’s a completely different way of thinking.
Try This With an App You Already Use
Here’s a simple exercise I found much more useful than memorizing another system design diagram.
Pick an application you use every day.
- Maybe Spotify.
- Maybe Instagram.
- Maybe Swiggy.
- Maybe a banking application.
Now ask:
1. Where is the state?
If one application server crashes, does the user lose their session?
2. What is cached?
Which data can be slightly outdated?
Which data absolutely cannot?
3. Where is consistency important?
Would stale data actually cause a problem?
4. What can be asynchronous?
Does sending a notification really need to happen before the user gets a response?
5. What kind of database guarantees are required?
What happens if two users update the same piece of data simultaneously?
6. What is the API contract?
If the backend changes tomorrow, which clients could break?
Suddenly, you’re not memorizing system design anymore.
You’re reasoning about a real system.
One Question I Wish More People Asked
When learning system design, we often ask:
“What should I learn next?”
- Redis?
- Kafka?
- Sharding?
- Load balancing?
- CDNs?
- Kubernetes?
But maybe the better question is:
“Why would I need this?”
Because once you understand the problem, the technology becomes much easier to remember.
You don’t memorize:
“Redis is used for caching.”
You remember:
“I have a read-heavy workload, my database is becoming a bottleneck, and I can tolerate slightly stale data. A cache might help.”
You don’t memorize:
“Kafka is used for asynchronous processing.”
You remember:
“This operation doesn’t need to block the user’s request, and I want downstream services to process it independently.”
That’s the difference between memorizing system design and understanding it.
Final Thoughts
I don’t think system design becomes difficult because there are too many technologies.
It becomes difficult when we learn those technologies without understanding the problems they solve.
Once these ideas become part of your mental model, you can approach a system you’ve never seen before and still have a starting point.
And that’s probably the most important skill in system design:
Not knowing every answer.
But knowing which questions to ask.
So here’s my question for you:
If you had to design Instagram today, which part would you worry about first — caching, database scaling, consistency, message queues, or something else?
I’d genuinely like to know how you would approach it.
From Tech By Neha Gupta
- 👏 Enjoyed the article? Don’t forget to leave a clap.
- 💬 Have thoughts or questions? Share them in the comments.
Before you go
- Please take a moment to like the post and follow the writer!
- Did you know that over 400,000 developers share what they’re building, learning, and discovering across our platforms every month? Learn how you can contribute here