System design

System Design for Developers: A Beginner’s Guide with a Mobile Angle

System design is the skill of deciding how clients, servers, databases and queues fit together so a product keeps working as users grow. This guide takes you from the basics to a mobile-specific worked example and a repeatable interview framework.

An engineer studying a system diagram drawn on a whiteboard
Photo: Pexels
In this article
  1. What is system design?
  2. What are the core building blocks of system design?
  3. What trade-offs should every developer understand?
  4. What is different about mobile system design?
  5. Worked example: designing an M-Pesa-style “send money” feature
  6. What is a good system design interview framework?
  7. How do you learn system design in 2026?
  8. Frequently asked questions

What is system design?

System design is deciding how the parts of a software product fit together so that it meets its requirements for scale, reliability, speed and cost. Code answers “how does this function work?”; system design answers “what runs where, what talks to what, and what happens when something breaks?”.

A useful way to see the difference: sorting a list is an algorithm problem. Showing 500,000 customers their M-Pesa statement within two seconds, on a patchy 3G connection, while a nightly report job is also running, is a system design problem. It involves a phone, an API, a database, maybe a cache, and a plan for failure.

When do junior developers need it?

Earlier than most people think. You do not need to design a platform for ten million users in your first year. But you will decide how to paginate a list, whether to cache a response, how often to retry, and where to store data locally. Those are design decisions with consequences. In design discussions, the junior developers who stand out can explain why they chose an approach, not just how they implemented it.

Mid-level interviews usually add a dedicated design round. If you are heading there, pair this guide with our software developer interview guide and keep your data structures fundamentals sharp, because good design depends on knowing what a hash map or a queue costs.

What are the core building blocks of system design?

Most systems are assembled from eight reusable parts: clients, APIs, load balancers, databases, caches, queues, CDNs and object storage. Learn what problem each one solves and you can read almost any architecture diagram.

Building blockWhat it solvesTypical example
ClientShows the UI and captures user intent; the part you do not controlAndroid app, iOS app, browser
API (server)Holds business rules and exposes data to clients in a stable contractREST or GraphQL service
Load balancerSpreads requests across many servers and removes unhealthy onesNginx, cloud load balancer
DatabaseStores data durably and answers queriesPostgreSQL, MySQL, MongoDB
CacheServes hot data from memory so the database is not hit every timeRedis, in-app memory cache
QueueMoves slow or bursty work out of the request so users do not waitMessage broker for SMS, emails, receipts
CDNServes static files from a location close to the userImages, app config, videos
Object storageCheap, durable storage for large filesProfile photos, PDFs, backups

SQL or NoSQL?

Choose SQL when your data has relationships and must stay correct (accounts, payments, orders). Choose NoSQL when you need flexible documents, very high write volume or simple lookups by key (chat history, sessions, feeds). SQL databases give you transactions and constraints; most NoSQL stores trade some of that for easier horizontal scaling. For a beginner product, a single well-indexed PostgreSQL database takes you surprisingly far.

What trade-offs should every developer understand?

Every design decision gives something up. The skill is naming what you are trading, not finding a perfect answer, because none exists.

  • Consistency vs availability. When a network splits, a system must choose between refusing requests to stay correct or answering with possibly stale data. A bank balance should be correct; a “likes” counter can be slightly stale.
  • Latency vs cost. Caches, replicas and CDNs make things faster and cost money and complexity. Spend where users feel it.
  • Vertical vs horizontal scaling. Vertical means a bigger machine; it is simple but has a ceiling and a single point of failure. Horizontal means more machines behind a load balancer; it scales further but forces you to think about shared state and data partitioning.
  • Simplicity vs flexibility. A monolith is easier to build, test and deploy. Splitting into services helps large teams, and hurts small ones when done too early.

A rule that has served me well: scale the boring way first. Add an index before a cache, a cache before a new database, and more servers before a rewrite.

What is different about mobile system design?

On mobile you do not control the client, the network or the update cycle. That changes your design more than any other factor, and it is the gap most generic system design tutorials skip.

Offline-first, sync and conflict resolution

Offline-first means the app reads and writes a local database (Room on Android, Core Data or SwiftData on iOS) and syncs with the server in the background. The UI never waits on the network. This matters in Kenya, where users move between Wi-Fi, 4G and dead zones in one trip. The hard part is conflicts: two devices edit the same record offline. Your options are last-write-wins (simple, loses data), field-level merging, or keeping a version number and asking the user. Pick per feature; a note can merge, a payment cannot.

Pagination, image caching and push notifications

  • Pagination. Never return an unbounded list. Prefer cursor-based pagination (“give me 20 items after this ID”) over page numbers, because new items arriving mid-scroll would otherwise cause duplicates.
  • Image caching. Use a memory cache plus a disk cache, request correctly sized images and serve them via a CDN. Libraries such as Coil on Android and the system’s URL cache on iOS handle most of this.
  • Push notifications. Treat push as a hint, not a guarantee. Delivery can be delayed or dropped, so the app must still fetch the true state when opened. Never put secrets in the payload.

API versioning and flaky networks

Old app versions live forever. Some users never update, and you cannot recall a build from a phone. So your API must stay backward compatible: add fields, do not rename or remove them; version endpoints (/v2/) when you must break things; and keep a “minimum supported version” check that can show a polite “please update” screen. For flaky networks, set timeouts, retry with exponential backoff and jitter, and make every write safe to repeat. That last point leads straight into the worked example.

For platform specifics, the official Android developer documentation and Apple developer documentation cover background work and storage in depth.

Worked example: designing an M-Pesa-style “send money” feature

Let us design a simplified peer-to-peer payment feature, the kind that lives inside a wallet or mobile money app. We will follow the same steps you would use in an interview.

Step 1 and 2: requirements and estimates

Functional: a user sends money to another user, sees the result, and views history. Non-functional: money must never be lost or duplicated, the API must tolerate retries, and it must work on weak networks. Out of scope for now: fraud scoring, reversals, bill payments.

Estimate with round, stated assumptions. Say 5 million daily active users averaging 2 transactions a day gives 10 million a day. Divide by roughly 100,000 seconds in a day: about 100 transactions per second on average, and perhaps 5 to 10 times that at peak. That is comfortably within reach of a well-tuned relational database, which is a useful conclusion: you do not need exotic technology.

Step 3 and 4: API and data model

One write endpoint and two reads are enough:

POST /v1/transfers          (Idempotency-Key header)
GET  /v1/transfers/{id}
GET  /v1/accounts/{id}/transfers?cursor=...&limit=20

Data model, in a relational database: accounts (id, balance), transfers (id, from, to, amount, status, idempotency_key unique) and a ledger_entries table where every transfer writes a debit row and a credit row. Store amounts as integers in the smallest unit (cents or shillings times 100), never as floating point.

Step 5: idempotency, the heart of payments

Imagine the user taps Send, the server debits the account, and the response is lost in a dead zone. The app retries. Without protection, the customer pays twice. The fix is an idempotency key: the app generates a unique ID once per user intent and sends it with every retry. The server stores it with a unique constraint and returns the original result if it sees it again.

// Kotlin: generate once per tap, reuse on retry
val key = UUID.randomUUID().toString()

suspend fun send(req: TransferRequest): Transfer =
    retryWithBackoff(times = 3) {
        api.createTransfer(req, idempotencyKey = key)
    }

Server side, wrap the debit, credit and key insert in one database transaction. If the insert violates the unique key, fetch and return the existing transfer instead of running it again. This is also how Safaricom’s Daraja callbacks should be handled: the same callback can arrive more than once, so deduplicate on the transaction ID.

Step 6 and 7: scaling and failure modes

  • Scaling reads. Cache account summaries and add read replicas for history. Keep the balance-changing path on the primary database.
  • Scaling writes. Run several stateless API servers behind a load balancer. If one database ever becomes the limit, partition by account ID.
  • Slow side effects. Push the SMS receipt and push notification onto a queue so the transfer response is fast and a failing SMS gateway never blocks a payment.
  • Failure modes. Server crashes mid-transfer (the transaction rolls back), a callback arrives twice (idempotency), the network drops after a send (the app polls GET /transfers/{id} using its key), and a pending status stuck too long (a reconciliation job compares your ledger with the provider’s records).

Notice what we chose: consistency over availability. If the database is unreachable, the honest answer is “try again”, not a guess.

What is a good system design interview framework?

Use the same seven steps every time. A fixed structure stops you freezing, shows the interviewer how you think, and keeps you from diving into details too early.

  1. Clarify requirements. Ask who the users are, what the core features are, and what is out of scope. Separate functional from non-functional needs.
  2. Estimate scale. Users, requests per second, storage per year. Round numbers and say your assumptions aloud.
  3. Define the API. List the main endpoints or events and their inputs and outputs.
  4. Sketch the data model. Entities, relationships, and which database type and why.
  5. Draw the high-level design. Client, load balancer, services, database, cache, queue. Walk one request through it end to end.
  6. Deep dive. Pick one or two risky parts (sync, hot rows, fan-out) and go deeper. The interviewer often steers you here.
  7. Failure modes and trade-offs. What breaks, how you detect it, what you gave up. Mention monitoring and retries.

For mobile roles, add a client slice at step 5: local storage, sync strategy, caching, and how an old app version behaves. Most candidates forget it, which makes it an easy way to stand out. More on preparing across all rounds is in our interview guide.

How do you learn system design in 2026?

Learn the building blocks, study real examples, then build small things that force you to use them. Reading alone does not stick; you need to make a decision and live with its consequences.

  1. Weeks 1 to 2: foundations. Understand HTTP, REST, how a database index works, and what a cache and a queue do. Revisit the table above until you can explain each row without notes.
  2. Weeks 3 to 4: study real designs. Read engineering blogs from companies that publish their architecture, and work through classic problems: URL shortener, chat app, news feed, payment system. Redraw each from memory.
  3. Weeks 5 to 8: build. Make a small app with a real backend: an offline-first notes app with sync, then add pagination, an image upload to object storage, and a queue for background jobs. Deliberately break it by turning off Wi-Fi mid-request.
  4. Ongoing: practise aloud. Explain a design to a friend or record yourself. Time-box to 35 to 45 minutes like an interview.

AI assistants such as Claude, Copilot and Cursor are now a useful sparring partner: ask them to critique your design, list failure modes you missed or play the sceptical interviewer. They are a poor substitute for understanding, because you will be the one on call when it breaks. Use them to check your thinking, not replace it.

If you want structure and feedback, our Professional track covers mobile architecture, offline sync and mock interviews, and you can see the details under the tracks. For a wider view of where this skill sits, read mobile app development in 2026.

Frequently asked questions

What is system design?
System design is the process of deciding how the components of a software product (apps, servers, databases, caches, queues) fit together to meet requirements such as scale, reliability, speed and cost. It sits above writing individual functions and below company strategy. Every feature that touches more than one machine involves it.
Do junior developers need to learn system design?
Yes, at a basic level. You will not design a whole platform in your first year, but you will make small design decisions daily: what to cache, how to paginate, how to retry a failed request. Interviews for mid-level roles almost always include a design round, so start early.
What is mobile system design?
Mobile system design is designing the whole system around the constraints of a phone: unreliable networks, limited battery and storage, and app versions you cannot force users to update. It covers offline-first storage, sync and conflict resolution, pagination, push notifications, image caching and API versioning.
What is a good framework for a system design interview?
Work through seven steps in order: clarify requirements, estimate scale, define the API, sketch the data model, draw a high-level design, deep dive into one or two risky areas, then discuss failure modes and trade-offs. Say your assumptions aloud at each step so the interviewer can follow your reasoning.
How do I learn system design in 2026?
Learn the building blocks first, then read how real companies solve specific problems on their engineering blogs, then build small projects that force you to use a cache, a queue and a database. Practise explaining designs aloud. AI tools can critique your design, but you must understand the trade-offs yourself.
Is SQL or NoSQL better for a payments app?
Use a relational SQL database for payments. You need transactions, constraints and strong consistency so balances never go wrong. NoSQL stores shine for very high write volumes, flexible documents or simple key-value lookups, which suit things like sessions, chat history or activity feeds.

Already code? Ship on Android and iOS in 12 weeks

The Professional Track: Kotlin + Swift, Compose + SwiftUI, AI-assisted development, weekly DSA and mock interviews. Evenings and weekends, KES 25,000 a month. Next intakes start 9 November 2026 and 11 January 2027. Free to apply.

Kevin Lagat
Kevin Lagat

Founder and lead instructor at Bridgetree Learn. Former Senior iOS Engineer at Safaricom, where he worked on the M-PESA app. He has shipped production apps for the UK's NHS, a Saudi digital bank and US SaaS companies, and trained Saudi Digital Academy cohorts through Coding Dojo. · GitHub