NoSQL
SQL vs NoSQL
Two engineers. Same brief: build a social platform where users have profiles, write posts, and follow each other. Engineer A reaches for PostgreSQL. Engineer B reaches for MongoDB. Same data. Same features. Completely different experience building, querying, and scaling it. This lesson puts both solutions side by side — same problem, line by line — so you can see exactly where each one wins and where each one struggles.
The Data Model — Designing the Same Thing Twice
Before writing a single query, both engineers have to decide how to structure their data. This is where the philosophies immediately diverge.
TABLE users
id, name, email, bio, avatar_url
TABLE posts
id, user_id, body, created_at
TABLE follows
follower_id, following_id
TABLE post_tags
post_id, tag
collection users {
_id, name, email, bio,
avatar_url,
following: [user_ids],
posts: [
{ body, created_at,
tags: ["tech","news"] }
]
}
The key design difference:
SQL normalises — it splits data into the smallest possible pieces and links them with foreign keys. Less duplication. Strict consistency. But reading requires JOINs.
MongoDB denormalises — it keeps related data together in one document. More duplication is accepted. But reading is one operation. No JOINs needed.
Round 1 — Writing Data
The scenario: A new user signs up and immediately writes their first post with two tags. Let's see how many operations each system needs.
-- Operation 1: insert the user
INSERT INTO users (name, email, bio)
VALUES ('Priya', 'p@mail.com', 'Dev');
-- Operation 2: insert the post
INSERT INTO posts (user_id, body)
VALUES (1, 'Hello world!');
-- Operation 3: tag 1
INSERT INTO post_tags (post_id, tag)
VALUES (1, 'tech');
-- Operation 4: tag 2
INSERT INTO post_tags (post_id, tag)
VALUES (1, 'intro');
// One insertOne — everything together
db.users.insertOne({
name: "Priya",
email: "p@mail.com",
bio: "Dev",
posts: [{
body: "Hello world!",
created_at: new Date(),
tags: ["tech", "intro"]
}]
})
Line by line — what each approach costs:
SQL — 4 round trips to the database
Each INSERT is a separate database call. In production, you'd wrap these in a BEGIN / COMMIT transaction block — that's 6 operations total. If the post insert succeeds but tag insert fails, you need rollback logic. More code. More failure points.
NoSQL — 1 round trip, atomic by default
insertOne writes the entire document — user, post, and tags — in a single atomic operation. Either the whole thing is saved or nothing is. No transaction block needed. No rollback logic. The document is the transaction boundary.
tags: ["tech", "intro"]
In SQL, each tag is a separate row in a separate table. In MongoDB, tags are just an array inside the document. To add a new tag later: $push. To find all posts with tag "tech": find({tags: "tech"}). One line.
Round 2 — Reading Data
The scenario: Load a user's profile page — their name, bio, and their last 3 posts with tags. This is a page that gets loaded millions of times a day.
SELECT
u.name, u.bio,
p.body, p.created_at,
pt.tag
FROM users u
JOIN posts p
ON p.user_id = u.id
JOIN post_tags pt
ON pt.post_id = p.id
WHERE u.id = 42
ORDER BY p.created_at DESC
LIMIT 3;
db.users.findOne(
{ _id: 42 },
{
name: 1,
bio: 1,
posts: { $slice: -3 }
}
)
-- SQL result (multiple rows, needs client-side grouping):
name | bio | body | created_at | tag
-------+--------+-------------------+---------------------+------
Priya | Dev | Shipped v2 today! | 2024-01-15 14:22:00 | tech
Priya | Dev | Shipped v2 today! | 2024-01-15 14:22:00 | work
Priya | Dev | Hello world! | 2024-01-10 09:00:00 | intro
-- MongoDB result (one object, ready to use):
{
name: "Priya",
bio: "Dev",
posts: [
{ body: "Shipped v2 today!", tags: ["tech","work"], ... },
{ body: "Hello world!", tags: ["intro"], ... }
]
}
Breaking down both results:
SQL — flat rows, duplicated data
Notice "Priya / Dev / Shipped v2 today!" appears twice — once per tag. Your app code has to loop through the rows and group them back into a post-with-tags structure. Extra work every time this page loads.
posts: {"$slice": -3}
$slice: -3 returns the last 3 items from the posts array. MongoDB does the slicing inside the database — you don't get all posts and discard them in your app. Efficient and clean.
MongoDB result shape
The result is already the exact shape your frontend needs. No grouping. No transformation. Hand it straight to your API response. This is called query-driven design — your data is stored in the shape you read it.
Round 3 — Changing the Schema
The scenario: Six months after launch, the product team wants a "verified" badge for trusted users. Engineer A and Engineer B both get the ticket at 9am. Here's how their day goes:
SQL Engineer A's morning
ALTER TABLE users ADD COLUMN is_verified BOOLEAN DEFAULT FALSE;
Total time: 1 day + a 2am wake-up
NoSQL Engineer B's morning
is_verified: true to the user document when verifying someone.
Total time: 6 minutes, no wake-ups
// MongoDB — mark a user as verified
// No migration. No ALTER TABLE. Just update the document.
db.users.updateOne(
{ _id: ObjectId("64f1a2b3c4d5e6f7a8b9c001") },
{ $set: { is_verified: true, verified_at: new Date() } }
)
What each line does:
updateOne({ _id: ... })
Find the specific user document by their unique ID. ObjectId is MongoDB's built-in ID type — a 12-byte value that encodes the creation timestamp, machine ID, and a counter. Globally unique without a central sequence.
$set: {"{ is_verified: true }"}
$set is a MongoDB update operator. It adds or updates only the fields you specify — it doesn't touch anything else in the document. If is_verified didn't exist before, it's created now. Other users who don't get this update simply don't have the field. No NULLs. No table-wide changes.
Head-to-Head — 6 Criteria
⚡ Write Speed
NoSQL skips strict locking and WAL synchronisation. Designed for high-throughput ingestion.
🔄 Schema Flexibility
Every field change in SQL is a migration. In NoSQL, just start using the new field.
🔗 Complex Queries
Multi-table aggregations, window functions, complex GROUP BY — SQL dominates here.
🏦 Data Consistency
For bank transfers, stock deductions — SQL's ACID guarantees are non-negotiable.
📈 Horizontal Scale
NoSQL was designed to shard across nodes from day one. SQL requires extra tooling.
🎓 Learning Curve
SQL is one universal language. Each NoSQL database has its own query API to learn.
Decision Tree — Which One Should You Use?
Run your project through this. Be honest with the answers — the most common mistake is answering based on what sounds exciting rather than what the data actually looks like.
(same fields for every record, unlikely to change)
PostgreSQL / MySQL
choose by scale
Mongo/Cassandra/Redis
what does data look like?
Real Company Stacks — What They Actually Run
The best engineers don't pick one side. Here's proof — real companies running both SQL and NoSQL in the same product:
| Company | NoSQL — used for | SQL — still used for |
|---|---|---|
| Twitter / X | Manhattan (Redis-like) for timelines. Cassandra for tweet storage. | MySQL for user accounts and billing. |
| Airbnb | MongoDB for listing content. Elasticsearch for search. | MySQL for bookings and payments. |
| Uber | Cassandra for trip data and driver location history. | PostgreSQL for financial transactions. |
| GitHub | Redis for caching, sessions, background jobs. | MySQL for repos, issues, pull requests. |
| Shopify | Redis for cart sessions. Kafka for event streaming. | MySQL for orders, products, customer records. |
The pattern is clear: NoSQL handles the high-volume, flexible, fast-changing data. SQL handles the financial and relational core. They work side by side, not against each other.
Teacher's Note
Every junior engineer I've seen struggle with this choice was trying to find "the winner." There isn't one. The question is never SQL vs NoSQL — it's always what does this specific data look like, and how will I read it? Answer that honestly and the right tool becomes obvious.
Practice Questions — You're the Engineer
Scenario:
Scenario:
last_login timestamp to a user document in MongoDB without overwriting any of their other existing fields — name, email, posts, everything else stays exactly as it is. Which MongoDB update operator do you use?
Scenario:
Quiz — You're the Architect
Scenario:
listings table has 80 columns, most NULL. Migrations are getting dangerous. What is the right fix?
Scenario:
Scenario:
Up Next · Lesson 5
CAP Theorem
The rule that governs every distributed database on the planet — and why you can never have consistency, availability, and partition tolerance all at once.