Picture Sharma Uncle's kirana store in Pune. For years he kept everything in one fat notebook: who bought what, who owes money, which supplier delivered how many bags of atta. It worked when the shop was small.
Then the shop grew. His son started taking phone orders while Uncle stood at the counter, and both wanted the notebook at the same time. Somebody wrote "Mrs Kulkarni, 2 kg sugar, paid" on a page that already said she owed ₹340, and now nobody knows which line is true. Finding "everything Mrs Kulkarni bought in March" means turning 200 pages. When a customer moves house, her old address is scribbled on forty different pages. One rainy day the notebook got wet.
So Uncle's son set up something better. One register lists customers, one row each, with a customer number. Another register lists products, again with a number each. The sales register is short and tidy: date, customer number, product number, quantity. To learn a customer's address, you look up her number in the customer register. When she moves, you change one line. And the son made rules: no sale without a real customer number, no quantity below one.
That is a database in a nutshell. Separate registers called tables, numbers that link them, rules that keep bad entries out, and a way to ask questions ("everything Mrs Kulkarni bought in March") that takes seconds instead of an afternoon. In this lesson you will meet the real thing, and run your first questions against it.
In a real productSwaad, a food delivery app
Every app you use on your phone sits on top of a . When you order a biryani, check your UPI history, book a Tatkal ticket on IRCTC or follow a cricket score, the app is reading and writing rows in a database somewhere.
In this section you will work with Swaad, a made-up food delivery app with real-looking data: 8 cities, 2,000 customers, 120 restaurants, 1,200 dishes, 300 delivery partners, 20,000 orders placed during 2025 and about 5,400 reviews. Every grey SQL box in these lessons has a Run button. Press it and your question runs on a real database inside your browser, loaded with Swaad's data. Nothing you do can break anything: each lesson page starts from a fresh copy.
Here is what is at stake for an app like Swaad:
Money. Every order is a payment. Lose one row and a customer was charged for food that, as far as the app knows, never existed.
Many people at once. On a Friday at 9 pm, thousands of people are placing orders, restaurants are marking food ready and delivery partners are updating where they are. All of them change the same data at the same moment.
Questions, all day long. "Where is my order?" from a customer. "How many orders did we get from Pune last week?" from a manager. "Which restaurants get the worst reviews?" from the quality team. Each is a question the data must answer quickly.
Rules. An order must belong to a real customer. A price must be above zero. A delivery partner can't deliver an order that was cancelled.
A spreadsheet can hold the rows. It can't do the rest well. That gap is why databases exist.
The ideaHow it works
(say "S-Q-L" or "sequel") is the language you use to talk to a database. Before you write any, it helps to know what you are talking to.
Tables, rows and columns
A database keeps its data in . A table looks like one sheet of a spreadsheet: a grid with a name.
Each is one thing: one city, one customer, one order.
Each is one fact that every row has: a name, a price, a time.
Swaad has a table called cities. It is the smallest one, with 8 rows. Press Run to see all of it:
SELECT *
FROM cities;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
You get 8 rows and 3 columns: id, name and state. Mumbai is city 1, Pune is city 6, Kolkata is city 8. The id column gives every row a number that is never repeated. That number is the row's : the one sure way to point at exactly one row.
Two things make a table stricter than a spreadsheet sheet:
Every column has a type.id holds whole numbers, name holds text. You can't type "hello" into a number column. This kind of value is the column's , and you'll learn the common ones later in this module.
There is no "row 7". A spreadsheet has numbered lines and a fixed order. A table is more like a bag of rows. Rows have no position; you find them by what's in them ("the city whose id is 6"), not by where they sit.
The second point surprises people, and it matters later: when you ask for rows without saying how to sort them, the database may hand them back in any order.
Why not a spreadsheet or a file?
Plenty of small businesses run on spreadsheets, and for a list of 200 customers that's fine. Here is what goes wrong as an app grows.
Many people at once. A spreadsheet file is opened, edited and saved as a whole. When two people change it at the same moment, the last one to save usually wins, and the other person's change vanishes without a warning.
A database is built for , many users working at the same time. Thousands of people can place orders together and every single one is saved, in full, or not at all.
Rules. A spreadsheet lets you type anything anywhere: "two hundred" in a price cell, a delivery date before the order date, an order for a customer who doesn't exist. A database lets you write rules, called , and checks every change against them. Break a rule and the change is refused.
Size. Spreadsheet programs slow down badly with a few lakh rows, and they load the whole file into memory to work on it. A database keeps data on disk in a form it can search without reading everything. Tables of crores of rows are routine.
Asking questions. In a spreadsheet, "how much did Pune customers spend on biryani in October?" means filters, copy-paste and a pivot table, by hand, every time. In a database you write the question once in SQL, and you can run it again tomorrow, or have the app run it a thousand times a second.
Safety. A database keeps a record of every change before it makes it, so a power cut in the middle of saving doesn't leave half an order behind. It can be backed up while it is being used. (The write-ahead log lesson shows how.)
What about plain files, like one text file per order? You would have to write all of the above yourself: locking, rules, searching, crash safety. Databases are decades of that work, already done and tested.
The database server and the app
When Swaad runs a database, it isn't a file on someone's laptop. It is a program called a , running on a machine in a data centre. The server owns the data files. Nobody touches them directly; everyone asks the server.
Place orderTrack orderRefund checkSQLSQLRitu's phoneKabir's phoneSupport teamSwaad app serverReports toolPostgres
Your phone never talks to the database. It talks to Swaad's app server, which checks who you are and what you're allowed to do. The app server then sends SQL to the database server over a network connection, gets back the rows, and turns them into the screen you see. The reports tool used by the support team does the same: it is one more app sending SQL to the same server.
Each message the app sends is a : a question ("show me order 2") or an instruction ("save this new order"). Here is what happens when you place an order:
Step by step4 steps
1
Your phoneSwaad app server
Tap Place order: 2 biryanis and a sweet
2
Swaad app serverPostgres
Save this order for customer 1289
3
PostgresSwaad app server
Saved as order 2
4
Swaad app serverYour phone
Order placed, arriving in about 35 min
The database server does real work for each query. It finds rows without reading every table from start to end, keeps different people's changes from clashing, checks the rules, and sends back only the rows that were asked for. The grid it sends back is called a .
Relational: tables that point at each other
Swaad's orders don't contain the customer's name, phone or city. They contain customer_id, a number. To know who placed an order, you look up that number in the customers table.
This is the core idea of a : each fact is stored once, in the table it belongs to, and other tables point at it by its id. A column that points at another table's id is a .
Why go to this trouble? Imagine Ritu Shah has placed 14 orders and changes her phone number. If her phone were written on every order, you would have to change 14 rows and hope you found them all. With one row in customers, you change one value, and every order that points at her sees the new number at once. The relational idea keeps facts from drifting out of step.
The database can also enforce the pointers. Swaad's orders.customer_id is a foreign key to customers.id, so an order for a customer who doesn't exist is refused. You'll see that happen in a minute.
Postgres, and other databases
There are many relational databases, and all of them speak SQL. In these lessons you use (full name PostgreSQL), a free and open-source one that many companies run. The database on this page is real Postgres, compiled to run inside your browser.
SQL is a standard, so most of what you learn works in MySQL, SQL Server, Oracle and SQLite too. Each database also adds its own extras. When a lesson uses something Postgres-only, it says so.
You'll also hear about databases, which store data as documents, key-value pairs or graphs rather than tables. They solve different problems, and the last module of this section covers them. Relational databases remain the default starting point for most apps, so that is where you start.
Meet Swaad's tables
Swaad has 8 tables. You can ask Postgres to list them:
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Here is what each one holds:
cities: the 8 cities Swaad works in.
customers: 2,000 people with a name, email, phone (some have none), city and the date they joined.
restaurants: 120 restaurants, 15 per city, each with a cuisine and a star rating.
menu_items: 10 dishes per restaurant, each with a price and whether it is vegetarian.
delivery_partners: 300 riders, each with a city and a vehicle (bike, scooter or cycle).
orders: 20,000 orders from 2025. Who ordered, from which restaurant, who delivered it, the status, the times, the total paid, the delivery fee and how they paid.
order_items: what was in each order, about 50,000 lines. One row per dish per order, with the quantity and the price paid.
reviews: about 5,400 star ratings, each tied to one order.
How many orders are there? Ask:
SELECT count(*)
FROM orders;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
The answer is one row with one number: 20000. You didn't scroll through anything. The database counted for you.
Don't worry about every word of these queries yet; the next few lessons take them apart slowly. Reading with SELECT never changes data, so press Run as often as you like.
For experienced engineers
Postgres runs as one server process that starts a separate backend process for each connection. Data lives in files split into fixed-size pages (8 KB by default), and the server keeps hot pages in a shared memory area. information_schema is a set of standard views describing the database's own tables; Postgres also has more detailed pg_* catalog tables. The copy of Postgres in this page is PGlite, a build of the real Postgres source for WebAssembly, so it behaves like the server version for everything in these lessons, except that it has one connection and no network.
Step by stepFollow one order through the tables
The question: who placed Swaad order number 2, what did they eat, where from, and who brought it? No single table knows all of this. You'll follow the ids from table to table, one query at a time.
Step 1: find the order.WHERE id = 2 asks for only the row whose id is 2.
SELECT *
FROM orders
WHERE id = 2;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Read it left to right. It was placed at 8:09 am on New Year's Day and delivered at 8:48 am, 39 minutes later. The customer paid ₹1,326 by UPI with no coupon (the empty coupon_code is a missing value, which the NULL lesson explains). The delivery fee was zero because Swaad gives free delivery on food worth ₹499 or more.
But who is customer 1289? The order only knows the number.
Step 2: follow customer_id to customers.
SELECT id, name, email, city_id
FROM customers
WHERE id = 1289;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Customer 1289 is Ritu Shah, ritu.shah1289@mail.in, in city 8. From the cities table earlier, city 8 is Kolkata.
Step 3: follow restaurant_id to restaurants.
SELECT id, name, cuisine, rating
FROM restaurants
WHERE id = 116;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Restaurant 116 is Annapurna Express, a biryani place with a 3.7 rating. Biryani at 8 am: Ritu knows what she likes.
Step 4: find what was in the order. The order_items table has one row per dish in an order.
SELECT *
FROM order_items
WHERE order_id = 2;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Three rows: menu item 1152 twice at ₹409, menu item 1156 once at ₹359 and menu item 1159 once at ₹149. Notice price is stored here too. That's on purpose: it records what Ritu paid that day, even if the restaurant raises its price next month.
Step 5: name the dishes.
SELECT id, name, price
FROM menu_items
WHERE id IN (1152, 1156, 1159);
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
1152 is Mutton Biryani, 1156 is Hyderabadi Dum Biryani and 1159 is Double ka Meetha, a sweet. Check the maths: 2 × 409 + 359 + 149 = 1,326. That's above ₹499, so delivery was free, and the total of ₹1,326 matches the order.
Step 6: who delivered it?
SELECT id, name, city_id, vehicle
FROM delivery_partners
WHERE id = 40;
Runs on a fresh copy of Swaad (food delivery), after the examples above it.
Partner 40 is Ramesh Thakur, on a bike, in city 8, Kolkata, the same city as Ritu and the restaurant.
So the full story: Ritu Shah in Kolkata ordered two Mutton Biryanis, a Hyderabadi Dum Biryani and a Double ka Meetha from Annapurna Express at 8:09 am on 1 January 2025, paid ₹1,326 by UPI, and Ramesh Thakur delivered it on his bike in 39 minutes.
Six small queries, six tables. Each fact lived in exactly one place, and the ids linked them. In the joins lesson you'll learn to do all six steps in one query.
Step 7: watch the rules work. Now try to break things. This tries to save an order with a status Swaad doesn't allow:
-- Error: new row for relation "orders" violates check constraint "orders_status_check"
INSERT INTO orders (customer_id, restaurant_id, status, placed_at, total, delivery_fee, payment_method)
VALUES (1289, 116, 'lost', '2025-12-31 21:00', 250, 30, 'upi');
This one is meant to fail.
Press Run and Postgres refuses: the status must be one of preparing, on_the_way, delivered or cancelled. This one tries an order for customer 99999, who doesn't exist:
Refused again, because customer_id must point at a real customer. No app code had to check either of these. The database guards its own data, whoever writes to it.
ScenariosYou're the engineer
Real situations you could face. Think about what you'd do before reading on.
Your manager keeps the order list in a shared sheet
You join a small cloud-kitchen startup in Pune. Orders come in from three apps, and the team copies each one into a shared online spreadsheet. Last week two orders vanished, and nobody knows who deleted them.
What you'd do: Suggest moving orders into a real database, even a small one. Start with a few tables: customers, orders and order items, each with an id, and rules such as "every order has a customer" and "quantity is at least 1". The team can still look at the data in a sheet-like tool that reads from the database.
Why: The sheet has no rules and no record of who changed what. A database refuses bad rows, keeps every change safe when people work at once, and can be backed up. The cost of setting one up is small next to the cost of lost orders and angry customers.
A teammate asks for 'the database file'
A new teammate wants to look at Swaad's orders and asks you to email them "the database file" so they can open it on their laptop.
What you'd do: Explain that the database is a server, not a file to pass around. Ask for read-only access for them instead, so they can connect with a SQL tool and run queries, or share the result of one specific query if they need a one-time list.
Why: Copying the data files off a running server doesn't give a usable copy, and it spreads customers' names, phones and emails onto laptops where nobody controls them. A database is shared by connecting to it, with access rules on who may read and change what.
The support team wants a customer's name on every order
Support finds it slow to look up who placed an order, and asks the developers to "add the customer's name and phone to the orders table".
What you'd do: Keep the name and phone in customers only, and give support a screen (or a saved query) that shows each order next to the customer's name by looking it up through customer_id. You'll learn to write this with a join in the next module.
Why: Copying the name onto every order means that when a customer changes their phone number, thousands of old orders still show the old one, and the "same" customer has different details in different places. Store the fact once and look it up when you need it.
What happens when…Situations
Real situations this idea runs into, and how well-built systems handle each one.
Half an order after a power cut
A small restaurant chain built its own ordering app that saved each order as a line in a text file and its items in a second file. One evening the office power went out in the middle of a save. The next morning the first file had the order, but the items file had only one of its three dishes. The kitchen cooked one dish; the customer had paid for three.
How it’s handled
Move to a database, which saves an order and its items together as one unit of work: either everything is saved or nothing is. That unit is called a , and the transactions lesson covers it in depth. The database also writes every change to a log before applying it, so after a crash it comes back with complete orders only, never half of one.
Orders for a customer who does not exist
An early version of an app stored orders in a database but had no rules on its tables. A bug in the sign-up screen deleted some new customers right after they placed their first order. Months later, the finance team found 1,200 orders pointing at customer ids that weren't in the customers table, and nobody could say who had paid for them.
How it’s handled
Add foreign keys, as Swaad has: orders.customer_id must point at a real row in customers. Before adding the rule, find and repair the existing broken rows, because the database won't accept a rule that the current data already breaks. From then on, the database itself refuses both an order for a missing customer and the deletion of a customer who still has orders.
The phone number that changed in only some places
A grocery app copied each customer's phone number onto every order, so delivery riders could call without an extra lookup. When customers changed their numbers, only the profile was updated. Riders kept calling old numbers from old orders that had been reopened for returns, and some reached complete strangers.
How it’s handled
Store the phone in one place, the customers table, and look it up through customer_id whenever it is needed. If an order must remember the exact details at the time it was placed (like a delivery address that should never change later), store that as a clearly named field such as delivery_address_at_order, on purpose, and never treat it as the customer's current details.
Watch outCommon mistakes
Thinking of a table as an ordered list
People coming from spreadsheets expect rows to stay in the order they were added, and write code that assumes "the first row is the oldest". It works for weeks, then one day the rows come back in a different order and the app shows the wrong "latest" order.
Better
Treat a table as a bag of rows with no built-in order. Whenever order matters, say so in the query with ORDER BY, which you'll learn soon. Find rows by what they contain, such as an id or a date, never by where they sit.
Storing the same fact in many places
To save a lookup, a developer copies the restaurant's name into every order, the customer's city into every review and the dish's name into every order item. Soon a restaurant renames itself and the app shows two names for the same place.
Better
Keep each fact in the table it belongs to and point at it with an id. Look it up when you need it. Copy a value on purpose only when you need to freeze it at a moment in time, such as the price paid on an order.
Letting the app be the only guard
The team writes careful checks in the app ("price must be positive", "customer must exist") and none in the database. Then someone writes a quick fix-up script that skips the app, and bad rows pour in.
Better
Put the important rules in the database as constraints, as Swaad does for statuses, prices and foreign keys. App checks give friendly error messages; database rules make sure nothing, from any app, script or person, can break them.
Trade-offsWhat it costs, and when not to use it
What a database gives you: many users reading and changing data at the same time without losing work; rules that keep bad data out; fast answers to questions over lakhs or crores of rows; crash safety and backups; one shared, trusted copy of the facts that every app and team reads.
What it costs: a server to run, watch, upgrade and back up (or a managed service to pay for); someone who understands tables and SQL; and some up-front thinking about which tables you need and how they link. Changing a table's shape later, on a busy app, takes care.
When a spreadsheet is fine: a one-person list, a few hundred rows, no app reading it, and nobody else changing it at the same time. A budget, a guest list, a quick analysis of data you exported.
When a relational database is the wrong first choice: some data doesn't fit tables well, such as huge streams of sensor readings, a social network's web of friendships, or full-text search over millions of documents. Specialised stores exist for those, and the "Beyond tables" module covers them. Even then, most apps keep their core records, such as users, orders and payments, in a relational database.
Interview angleIn an interview
In SQL rounds, interviewers assume you know what tables, rows, columns and keys are, and may test it with a quick "what is a primary key, what is a foreign key?" Answer with an example: "In a food app, orders.id is the primary key; orders.customer_id is a foreign key pointing at customers.id, so an order can't belong to a customer who doesn't exist."
In system design rounds, the database is almost always one box on your diagram. Say what's in it and why it's relational: "Users, restaurants, orders and payments go in Postgres. They have clear relationships, we need rules like foreign keys, and orders and payments must never be half-saved."
Likely follow-ups:
"Why not store orders in files, or in a spreadsheet?" Many writers at once, rules, crash safety, fast queries over large data, and backups while running. Files give you none of that without building it yourself.
"Why keep the customer's name out of the orders table?" Each fact is stored once, so it can't drift out of step. Look it up through customer_id when you need it.
"When would you not use a relational database?" When the data or the access pattern fits another store better, such as a cache for sessions or a search engine for full-text search, usually alongside the relational database, not instead of it.
Quick checkCheck your understanding
Words in this lessonThe jargon, in plain English
Every technical word used above. In the lesson, tap any word with an ⓘ to see its meaning right there.
Column
One named fact that every row of a table has, such as name or price. Every value in a column is the same kind of value.
Concurrency
Many things happening at the same moment, like thousands of people pressing "Book" at exactly 10:00:00.
Constraint
A rule the database checks on every change, such as 'price must be above zero' or 'no two customers share an email'. A change that breaks it is refused.
Data type
The kind of value a column holds, such as whole numbers, exact amounts of money, text, true or false, or dates. The database refuses values of the wrong kind.
Database
A program that stores data safely and lets you query it, even after restarts and crashes.
Database server
The program that owns a database's files and answers questions from apps over a network connection, so many apps and people can share one copy of the data safely.
Foreign key
A column that holds the id of a row in another table, with a rule that the id must exist there. An order's customer_id pointing at customers is one.
NoSQL database
A family of non-relational databases (key-value, document, wide-column, graph) that trade some features for flexibility or scale.
Postgres
PostgreSQL, usually called Postgres: a free, open-source relational database that many apps and companies run.
Primary key
A value that is unique to each row of a table, such as an order number, so the database can always find exactly one row by it.
Query
A question or instruction sent to a database, written in a language such as SQL. The database answers with rows, or with how many rows it changed.
Relational database
A database that keeps data in tables and links them with shared ids, so each fact is stored once and tables can be combined when you ask a question.
Result set
The grid of rows and columns a database sends back for a query. It has its own column names and is not stored anywhere unless you save it.
Row
One entry in a table, such as one order or one customer, with a value for every column.
SQL database
A relational database: data in tables with fixed columns, queried with SQL, with strong guarantees about consistency.
Table
A named grid of data inside a database. Each row is one thing, such as one order or one customer, and each column is one fact about it.
Transaction (in a database)
A group of changes the database treats as one: either all of them are saved or none are. Saving an order and its items together is a transaction, so you never end up with half an order.
Enjoyed this? Keep going for free
This lesson is a free preview. Sign up free to save your progress and earn XP, and to unlock all 336 lessons, the simulator challenges and mock interviews.