System Design · Databases

SQL vs NoSQL, Partitioning & Indexing

How to choose a data store, split it when it grows too big, and make it find rows fast — the three database decisions every system-design interview circles back to.

Updated August 2026 · Read ~22 min · Scope: the live session (SQL/NoSQL, sharding, indexing)


// the big idea

Databases force three separate choices, and they are independent. SQL vs NoSQL trades strong consistency against horizontal scale. Partitioning splits one big store into many so it can hold more and spread load. Indexing buys fast reads (O(log n)) with extra space and slower writes. In an interview you win by naming the trade-off out loud — there is no single right answer, only a justified one.

Section 1Notebook Summary

A dense, hand-copyable revision sheet. Everything here is derived from the detailed notes below.

SQL vs NoSQL — the 6 axes

Axis SQL NoSQL
Data / schema structured, fixed schema unstructured, flexible/schemaless
Scaling vertical (up); horizontal = hard (sharding) horizontal (out) — built for it
Consistency ACID → strong BASE → eventual
Queries joins, aggregation, nested — powerful simple lookups, limited joins
Best for banking, payments, inventory social, chat, logging, analytics

Eventual consistency

Partitioning (splitting the data)

Partition criteria

Method Rule Watch
Hash / key id % N uniform; resharding hard → consistent hashing
Range age 0–18 → A uneven if ranges skewed
List country ∈ {…} value change → repartition
Round-robin rec# % N even, but no locality; range Q bad
Composite country + hash best balance; complex

Indexing (finding the data fast)

Index types


Section 2Detailed Study Reference

You launch a bank that pays 40% interest, and a million people rush to open accounts. Two problems land at once. First, when Priya transfers ₹10,000 to Sam, the money must never vanish or appear twice — you cannot be "roughly right" about balances. Second, a single database server cannot hold a million users, let alone a billion. One demand wants absolute correctness; the other wants raw scale. Almost every database decision in system design is a negotiation between those two pulls — and the three tools below are how you negotiate.

2.1What a database choice actually decides

A database is just a place to store data — but which one you pick decides what you can promise your users. Both SQL and NoSQL stores hold data; the difference is the guarantees and the shape of the data they are good at. Before any brand name, understand the structured-store idea (a SQL database) versus the flexible-store idea (a NoSQL database).

Analogy · the filing cabinet vs the junk drawer

SQL is a filing cabinet — every folder has the same labelled tabs (name, roll number, year), so you can cross-reference and audit precisely. NoSQL is a labelled junk drawer — each item can carry different fields, so you dump things in fast and sort out shape later. Neither is "better"; they suit different messes.

SQL stores fit structured, well-defined data: the schema — the table name, columns, data types, constraints (primary key, foreign key, unique, not-null), and relationships between tables — is fixed before you insert a single row. A school record always has a name, a roll number, a year; the shape never changes. NoSQL stores fit unstructured or semi-structured data where one record has five fields and the next has eleven, and you cannot predict them in advance. The schema is flexible and easy to evolve.

SQL examples

MySQL, PostgreSQL, Microsoft SQL Server, Oracle.

NoSQL examples

Amazon DynamoDB, Apache Cassandra, MongoDB, Azure Cosmos DB.

💡 TIP · interview register

You are not expected to have used every product. If asked about SQL Server and you only know MySQL, say so — that is fine. Speak in terms of SQL vs NoSQL properties, and only name a specific engine if you can defend it, because naming one steers every follow-up question toward it.

2.2Scaling: up vs out

Scaling is how you grow to handle more load; the two directions are "bigger machine" and "more machines." SQL databases primarily scale vertically — you add CPU, RAM, or storage to a single server (make the box bigger). NoSQL databases are designed to scale horizontally — you add more servers to a cluster (add more boxes). Horizontal scaling is possible in SQL too, but it is harder and is achieved through sharding, which is why NoSQL was invented in the first place: to store billions or trillions of records that no single machine could hold.

VERTICAL (SQL default) 1 big box ↑ HORIZONTAL (NoSQL native) + more boxes →
Vertical: one server grows. Horizontal: the cluster grows.

2.3Consistency: ACID vs BASE

Consistency is the promise that everyone sees correct, agreed-upon data. SQL databases support ACID transactions, giving strong consistency — ideal wherever correctness is non-negotiable. NoSQL databases follow the BASE principle (Basically Available, Soft state, Eventual consistency): they favour staying available and scaling wide, and let all copies agree eventually.

ACID (SQL)

Atomicity · Consistency · Isolation · Durability. A set of properties that keep transactions reliable even through a crash or power cut.

BASE (NoSQL)

Basically Available · Soft state · Eventual consistency. Trades instant agreement for availability and scale.

The four ACID letters, in plain words:

Letter Means In one line
Atomicity all or nothing a transaction fully completes or rolls back to the previous state — no half-done.
Consistency valid → valid every rule/constraint holds; if a balance is 10,000, all replicas show 10,000.
Isolation no interference concurrent transactions behave as if run alone; B won't read A's uncommitted change.
Durability survives crash once committed, the change persists even if the DB crashes right after.
Analogy · money transfer

Atomicity is the ATM that either dispenses your cash and debits your account, or does neither — it never debits without paying out. That is why banking, payments, and airline booking live on SQL: a partial transaction is a disaster.

Some NoSQL engines (MongoDB, Cosmos DB) do offer transactions, but with different trade-offs than a traditional SQL system. The rule of thumb: if you cannot compromise on correctness, start with SQL.

2.4Eventual consistency, concretely

Eventual consistency is the window during which a write on the master database has not yet been copied to every replica. A write lands on the master (primary) DB, then propagates to the slave (replica) DBs. If a read hits a replica during that copy window — say it takes 5 minutes — it sees stale data. Once propagation finishes, every copy agrees. Hence "eventual."

MASTER DB bal = 10,000 replicate (lag ≈ 5 min) SLAVE A SLAVE B SLAVE C (stale)
During the copy window, a read on Slave C can return the old value.
⚠ PITFALL · eventual consistency is a NoSQL trait

Don't say SQL is "eventually consistent" by default. In a strongly-consistent SQL system you take a write lock so no one reads a half-written record — the cost is added latency, since readers block during the write. SQL can expose looser reads via versioning/isolation levels, but its default posture is strong consistency. Eventual consistency is the design choice you associate with NoSQL. [!] The instructor initially called SQL's MVCC behaviour "eventual consistency"; the precise framing is that it's a tunable isolation/locking trade-off, not the same replica-lag eventual consistency as NoSQL.

2.5Queries and when to choose which

SQL's query language is its superpower. It supports joins, aggregation, nested queries, and complex reporting — you can hang a BI dashboard straight off it. NoSQL is optimised for simple lookups with limited joins, and the query interface depends on the engine. So for rich analytics, SQL usually has the edge.

Choose SQL when…

Correctness & structure

  • Structured data, predefined schema, strong relationships
  • ACID transactions are critical (money transfer, payments, airline booking)
  • Complex queries: joins, aggregation, analytics/BI on top
  • Data integrity: foreign keys, referential integrity
  • Moderate scale (shard if you must)
Choose NoSQL when…

Scale & flexibility

  • Flexible/evolving schema; you can't predict fields
  • Horizontal scale: millions of users, massive datasets
  • Logging, time-series, event streaming, analytics feeds
  • Geo-distributed, fault-tolerant, high read/write throughput
  • No single point of failure needed
⭐ TRICK · you are allowed to use both

The instinct that "it must be one or the other" is wrong. A real system mixes stores per component. Amazon: SQL for payments (consistency), NoSQL for the product catalog (scale). An online game: NoSQL for real-time state, leaderboards, sessions (low latency, millions of players), SQL for accounts, purchases, and friend graphs (strong consistency). Answer by component, not by dogma.


2.6Partitioning: splitting one big store

Partitioning divides a large database into smaller, independent segments to improve performance, scalability, availability, and manageability. Each partition holds only a subset of the data. The banking scenario from the hook is exactly why: consistency says "stay on SQL," but a billion users won't fit on one box — so you split.

Analogy · one phone book vs many

A single national phone book is unusable. Split it by region (rows go to different books) and every lookup touches a thin book. That is sharding. Alternatively, keep names in one book and photos in another (split by column). That is vertical partitioning.

Horizontal partitioning (sharding) — split by rows

Different rows live on different servers, chosen by a value. Users 1–1M on server A, 1M–2M on server B; or zip < 10,000 on A, ≥ 10,000 on B.

Vertical partitioning — split by columns/features

Different columns/features live on different servers. Instagram: user profile on server A, followers on server B, photos on server C.

HORIZONTAL · by rows rows 1–1M → A rows 1M–2M → B rows 2M–3M → C VERTICAL · by columns profile→A followers→B photos→C
Horizontal cuts across rows; vertical slices down columns.
Sharding trade-off

+ improves scalability, reduces per-server DB size. − risk of uneven distribution (one shard becomes a hotspot); cross-shard queries get complex.

Vertical trade-off

+ feature isolation, better resource use, easy to implement. − one feature (e.g. a celebrity's followers) can still outgrow its single server, forcing further partitioning.

⭐ TRICK · stack them

Vertical's weakness (one feature outgrows its box) is fixed by then applying horizontal partitioning on that feature. Virat Kohli has 10M followers on the followers server? Hash-shard the followers table. Partitioning strategies compose.

How requests reach the right shard

The routing lives in the application code or a dedicated DB router/proxy — placed between the backend services and the shards — using the shard key (e.g. user ID). Some managed databases route for you.

Client→ Load balancer→ Backend service→ DB router (shard key)→ Shard 1 / 2 / 3
⚠ PITFALL · the load balancer doesn't shard

A standard load balancer spreads incoming requests across servers — it does not decide which shard holds a given row. Sharding is done by the application or a database router using the shard key. Conflating the two is a classic interview slip.

Partition criteria

Method How it decides Pro Con
Hash / key partition = id % N uniform, prevents hotspots adding/removing a server reshuffles data → use consistent hashing
Range value bands (age 0–18 → A) efficient range queries, intuitive uneven if a range is dense
List predefined value sets (country ∈ Nordic) logical grouping, easy manage value changes force repartitioning
Round-robin rec# % N, one each in turn dead simple, even spread no data locality; range queries inefficient; ignores load
Composite combine two (country + hash) best load balancing, handles huge sets complex to implement & maintain
💡 TIP · why sharding also speeds reads

Splitting 10M rows across 10 shards isn't only about capacity — each search now scans a smaller set, so queries get faster too. Scale and speed both improve, and indexing (next) then speeds lookups inside each shard.


2.7Indexing: finding rows fast

An index is a data structure that speeds up data retrieval so the database can locate rows without scanning the whole table. Without it, a query does a full table scan — checks every row — and gets slower as data grows. With it, the database jumps straight to the record.

Analogy · the back of a book

To find "mitochondria," you don't read every page — you flip to the index, read the page number, and jump. The index is a small sorted structure that points into a big unsorted body. That is exactly what a database index does.

Internally, most relational databases build indexes as B-trees / B+ trees (some support hash indexes). That is what turns the search from O(n) (scan every row) into O(log n) (tree descent). On millions of rows, that is the difference between "instant" and "the user gave up."

NO INDEX · O(n) scan check check found ✓ INDEX · O(log n) root row ✓ 2–3 hops
Scan touches every row; the B-tree index descends in a few hops.
create a secondary index, then query the indexed column
-- Without an index: full scan, O(n)
SELECT * FROM employee WHERE employee_id = 101;

-- Build an index on the column you filter by
CREATE INDEX idx_employee_id ON employee(employee_id);

-- Now the same query descends the B-tree: O(log n)
SELECT * FROM employee WHERE employee_id = 101;

How it works: the index stores value → row location. The DB searches the (sorted) index, gets the row number, then reads that row from the table and returns the record.

⚠ PITFALL · the index only helps the column you built it on

If you index email but query WHERE role_number = 7, the index is useless and you're back to a full scan. Index the columns you actually filter, join, sort, or group by — not whichever felt important.

⭐ TRICK · sharding and indexing solve different problems

Sharding splits data across servers for scale; indexing speeds search inside a shard. You shard on user ID, but if you search by email you still full-scan every shard unless each shard has an email index. Routing by shard key gets you to the right box; the index gets you to the right row. You need both.

💡 TIP · space-for-time is the whole game

An index costs extra disk space and slows writes — and that is the right trade. Spending space to cut time complexity from O(n) to O(log n) is exactly the pattern interviewers want to hear you reach for.

Index types

Primary

Auto-created on the primary key. Values unique, no nulls; often clustered by default.

Secondary

On a non-PK column to speed searches. Duplicate values allowed.

Clustered

Sets the physical storage order of rows. One per table (data has one physical order). Great for range queries; inserts/updates may reorganise data.

Non-clustered

Separate structure of value → pointer to row; doesn't change physical order. Many allowed. Behaves like an inverted index (tokens → row list).

Unique

Forbids duplicate values in the indexed column (e.g. email).

Covering

Holds every column the query needs, so the DB answers from the index alone — no table read. Very fast, less disk I/O.

CLUSTERED · rows physically in order 101 · John …full row here 102 · Ellis … 103 · Bob … 104 · David … NON-CLUSTERED · value → pointer Ellis → row 8 Bob → row 3 John → row 12 table
Clustered = the table itself is sorted. Non-clustered = a pointer list beside it.

When to index — and when not to

Create an index on…
  • Columns in WHERE often
  • Columns frequently JOINed
  • Columns in ORDER BY / GROUP BY
  • High selectivity (many unique values): user_id, email, order_id
Avoid an index when…
  • Table is small
  • Few unique values (gender, status)
  • Column changes frequently
  • Write-heavy workload (unless reads justify it)
Indexing — pros Indexing — cons
Fast SELECTs, fewer full scans Extra disk space
Faster sort / filter / range queries Slower INSERT / UPDATE / DELETE
Better JOIN performance Index needs maintenance as data changes

2.8Interview & practice Q&A

Q1. "Why SQL and not NoSQL?" — how do you answer without getting cornered?

Anchor on the requirement, not the brand: "The data is structured with strong relationships and the workload needs ACID guarantees — money can't be off by a cent — so I'll use a SQL store." Speak in properties (schema, consistency, query power) and only name an engine if you can defend it, since a brand name invites deep follow-ups. Signal that scale is handled via sharding if pressed.

Q2. What's the difference between BASE and ACID?

ACID (SQL) guarantees each transaction is atomic, consistency-preserving, isolated, and durable — strong consistency at the cost of scaling friction. BASE (NoSQL) is Basically Available, Soft state, Eventual consistency — it stays available and scales horizontally by letting replicas converge over time. You pick ACID when correctness is non-negotiable and BASE when availability and scale dominate.

Q3. A read returns a stale value right after a write. What's happening and where?

That's eventual consistency: the write landed on the master DB but hadn't propagated to the replica the read hit. During the copy window, replicas serve old data; once replication completes, all copies agree. It's characteristic of NoSQL/replicated setups. To avoid it you'd read from the master or use a strongly-consistent read, trading latency or availability for freshness.

Q4. Horizontal vs vertical partitioning — one line each, with the failure mode.

Horizontal (sharding) splits by rows across servers — fails via uneven distribution (hotspots) and hard cross-shard joins. Vertical splits by columns/features across servers — fails when one feature (a celebrity's followers) outgrows its single server, which you then fix by horizontally sharding that feature.

Q5. Does the load balancer route to the correct shard?

No. A load balancer distributes incoming requests across servers; it doesn't know shard keys. Shard routing is done by the application code or a dedicated DB router/proxy that computes the target shard from the shard key (e.g. user ID). Some managed databases provide built-in routing.

Q6. Round-robin vs hash partitioning — why prefer one?

Round-robin (rec# % N) gives a dead-simple even spread but ignores data locality and load, and makes range queries inefficient because related rows scatter. Hash partitioning (id % N) distributes uniformly and prevents hotspots, but adding/removing a server reshuffles almost everything — which is why consistent hashing exists.

Q7. If a table is already sharded on user_id, do I still need an index?

Yes — they solve different problems. Sharding scales storage and cuts rows per shard; it doesn't make search within a shard fast, and it only helps routing when you query by the shard key. Search by email while sharded on user_id and you full-scan every shard unless each shard indexes email. Index for speed inside the shard; shard for scale across shards.

Q8. Clustered vs non-clustered index?

A clustered index defines the physical order of the rows themselves — one per table, excellent for range queries, but inserts/updates may reorganise storage. A non-clustered index is a separate structure of value → pointer-to-row that leaves physical order untouched; you can have many, and it's ideal for frequent searches on non-primary columns (conceptually an inverted index).

Q9. Isn't an index just wasted space?

It costs extra disk space and slows writes, but that's an intentional space-for-time trade: it collapses reads from O(n) to O(log n). On large, read-heavy tables that's overwhelmingly worth it. You avoid indexes on tiny tables, low-selectivity columns (gender/status), volatile columns, and write-heavy workloads where the maintenance cost outweighs the read gain.

2.9Key takeaways

ACID → strong BASE → eventual vertical = up horizontal = out shard = rows vertical part = cols shard key routes index → O(log n) space for time shard ≠ index

Section 3Microexercises

Reading proves you recognise the ideas. These prove you can use them. Attempt each before opening the walkthrough.

3.1Build-it — pick the store, then index it

// your task

You're designing a ride-hailing app. Two data needs:

(a) Wallet/payment ledger — every debit/credit must be exact and survive crashes.
(b) Live driver-location pings — millions per minute, schema may add fields, occasional staleness is fine.

Requirements:

Expected result: (a) SQL — ACID/strong consistency. (b) NoSQL — horizontal scale + flexible schema. Plus a secondary index on rider_id.

Show the full walkthrough

Step 1 — classify each need by its dominant requirement. The wallet's non-negotiable is correctness: a debit without a matching credit is a disaster, and it must survive a crash. That is the ACID signature → SQL. The location pings are the opposite: enormous write volume, an evolving payload, and a stale position for a second is harmless. That is horizontal scale + flexible schema → NoSQL.

Step 2 — index the query you'll actually run. Riders filter transactions by rider_id, which is not the primary key of the ledger (the PK is the transaction id). So build a secondary index on rider_id — non-PK, duplicates allowed (a rider has many transactions).

wallet ledger · SQL
-- (a) wallet → SQL, chosen for ACID / strong consistency
-- (b) driver pings → NoSQL, chosen for horizontal scale + flexible schema

-- secondary index: rider_id is not the PK, and repeats per rider
CREATE INDEX idx_txn_rider ON transactions(rider_id);

-- the lookup this accelerates (O(n) full scan -> O(log n) tree descent)
SELECT * FROM transactions WHERE rider_id = 4821;

Why: you matched each store to its dominant requirement (correctness vs scale) instead of forcing one database everywhere, then indexed the exact column the read filters on — the space-for-time trade that turns a full scan into a few tree hops.

3.2Diagnose-it — the index that never fires

// your task

A junior engineer complains their query is "still slow even though I added an index." Their table is sharded on user_id. Here's what they did:

reported as slow
CREATE INDEX idx_user_email ON users(email);

-- the query that's still slow:
SELECT * FROM users WHERE phone_number = '555-0142';

Requirements:

Expected result: a full table scan across every shard; the index is on the wrong column.

Show the full walkthrough

Predicted failure: no error is thrown — it silently runs a full table scan, and because the query filters on phone_number (not the shard key), it scans every shard. Slowness, not an exception, is the symptom — which is exactly why it's confusing.

Root cause: the index is built on email, but the query filters on phone_number — an index only accelerates the column it was built on.

Step 1 — index the column you actually filter.

the fix
-- build the index on the queried column
CREATE INDEX idx_user_phone ON users(phone_number);

-- now this descends the B-tree inside each shard: O(log n)
SELECT * FROM users WHERE phone_number = '555-0142';

Step 2 — why sharding doesn't save you. The shard key is user_id. A query by user_id routes to exactly one shard. But phone_number is unrelated to the shard key, so the router can't narrow it down — the request fans out to all shards, and within each shard, without a phone index, it's a full scan. Sharding gives scale; the index gives speed inside the shard. You need the index regardless.

Why: this is the "index only helps its own column" pitfall stacked on the "sharding ≠ indexing" trick — the two most common ways a query stays slow after someone "added an index."

match store to requirement index the filtered column non-PK → secondary wrong column → full scan non-key filter → all shards shard for scale, index for speed