Partly. You can learn the vocabulary and the standard patterns of system design without writing code: what a load balancer does, why queues exist, what the CAP theorem says. You can’t learn to make design decisions you can rely on that way. Every box on a system design diagram is a running program with limited memory, CPU and connections, and every arrow is a network call that can be slow, arrive twice or fail halfway. Until you’ve built and debugged software yourself, those boxes and arrows stay abstract.
Here’s what you can learn from the outside, what you’ll miss, and the smallest amount of coding that closes the gap.
What you can learn without writing code
- The building blocks. What load balancers, reverse proxies, caches, message queues such as Kafka and RabbitMQ, object storage, and SQL and NoSQL databases are for.
- The classic trade-offs. The CAP theorem, ACID vs. BASE, scaling up vs. scaling out, synchronous vs. asynchronous calls.
- The business side. Where to draw the boundaries between parts of a system, compliance rules, and what a cloud setup will cost each month. Enterprise frameworks such as TOGAF belong here too.
- Combining managed services. Putting ready-made services together to solve a business problem, for example Auth0 for logins, Amazon S3 for files and Stripe for payments.
For some jobs this is enough. A solution architect who coordinates vendors, a business analyst or a product manager mostly needs to know what each piece does and what it costs, and can rely on engineers for the rest.
What you can’t master without code
How computers behave under load
Writing code teaches you how memory, disks and networks actually behave. A network round trip inside a data center takes thousands of times longer than reading from memory. A missing database index turns a quick lookup into a scan of every row. A database accepts only so many connections at once.
Take connection pools. Each app server keeps a pool of open database connections. Twenty servers with twenty connections each need 400 connections, and PostgreSQL allows 100 by default. On a diagram, “add more app servers” looks like the fix for more traffic. On the real system, the database starts refusing connections and requests fail. People who’ve watched a pool run dry under load spot this at once. People who’ve only drawn diagrams usually don’t.
How systems fail
Distributed systems rarely fail cleanly. A message arrives twice, or after a newer one. A request times out after the work was already done. Two users grab the last seat at the same moment. You don’t feel how hard event-driven designs are until you’ve chased bugs like these (we wrote about one kind in why event-driven architectures cause race conditions).
Here’s the most common one in a few lines of Python. A payment goes through, but the response is lost on the way back, so the client retries:
charges = [] # what the payment provider has recorded
seen = {} # idempotency key -> the result of the first request with it
def charge(amount, key=None, lose_response=False):
if key is not None and key in seen:
return seen[key] # a repeat: return the first result, charge nothing
charges.append(amount)
result = {"charged": amount, "id": len(charges)}
if key is not None:
seen[key] = result
if lose_response:
raise TimeoutError("the charge went through, but the response was lost")
return result
def pay(amount, key=None):
try:
return charge(amount, key, lose_response=True)
except TimeoutError:
return charge(amount, key) # the client can't tell what happened, so it retries
pay(50)
print("without a key:", charges)
charges.clear()
pay(50, key="order-1042")
print("with a key: ", charges)
Output:
without a key: [50, 50]
with a key: [50]
Without a key, the customer paid twice. An idempotency key is an ID the client sends with each request so the server can recognize a repeat and return the first result instead of doing the work again. Stripe’s API accepts one for exactly this reason. On a diagram, both versions are the same arrow.
What makes a design easy to build and maintain
A design also has to be buildable, testable and debuggable by the people who will work on it. If you’ve never spent days following one request through the logs of five services, or untangling two modules that depend on each other, you won’t notice when a design adds that kind of work for every engineer on the team.
The trust of the engineers who build it
Engineers are wary of architects who hand down designs without knowing what implementing them involves. If you can’t talk about API contracts, data formats, timeouts and error handling in terms they recognize, they’ll quietly build around your design.
Where the line is
| Without code, you can | With coding experience, you can also |
|---|---|
| Explain what a cache is for | Predict when it serves stale data, or when many requests miss at once and all hit the database |
| Draw a queue between two services | Handle messages that arrive twice or out of order |
| Say “add more app servers” | Check whether the database can take the extra connections |
| Name the CAP trade-off | Decide what your app shows a user while a replica is behind |
| Pick managed services | Predict what their limits and outages will do to your system |
The smallest amount of coding that closes the gap
You don’t need years of low-level programming. You need to have built one small system and watched it break.
- Learn one backend language. Python, Go or TypeScript are all fine. Enough to write a small web service is plenty.
- Build a small API with a real database. A URL shortener is a good choice: two endpoints (create a short link, follow it) on top of PostgreSQL. It’s also one of the most common system design interview questions, so you’ll know it from both sides.
- Break it on purpose. Send it many requests at once with a load-testing tool such as k6 or Locust. Stop the database in the middle of a request. Add network delay (on Linux,
tc netemdoes this). Retry requests and look for duplicates. Drop an index and watch the response times. - Fix what broke, then measure again. Add timeouts, retries with idempotency keys, a limit on the connection pool and the missing index. Each fix shows you why that pattern exists.
A few weekends of this changes how you read every system design diagram afterwards.
If you’re preparing for system design interviews
Interviewers use follow-up questions to find out whether you’ve built things: “What happens if this call times out?”, “What if the message is delivered twice?”, “How many connections will the database need?” Answering them well takes the implementation knowledge above.
AlgoCademy’s system design lessons are built around these questions, with lessons on timeouts and retries, idempotency keys, concurrency and locks, and caches that go wrong. The Foundations lessons are free, including latency numbers and an eight-step framework for the interview. For a broader study plan, see how to learn system design and architecture.
