Thuta Learning
BasicData & Databasesbeginner

Primary Keys and Foreign Keys

What you'll walk away with

  • Explain the core ideas behind Primary Keys and Foreign Keys
  • Read the diagram/table and identify the shape of the data model, schema, or architecture
  • Explain how this concept or system choice applies to a real project

Build the mental model

A primary key is the value that uniquely identifies one specific row in a table — no two rows share it, and it's never left empty. In a users table, users.id = 42 always means exactly one user, forever.

Key typeDescription
Natural keya real-world, business-meaningful value you already have, like an email
Surrogate keya value generated purely for identification, like an auto-incrementing integer or a UUID

Neither is automatically better: natural keys can change (people change emails), while surrogate keys are stable but carry no business meaning on their own. Real systems often use both patterns together.

A foreign key is different: it's a column in one table that stores the primary key value of a row in another table, creating a link. orders.user_id is expected to match some row's id in the users table.

Storing just a pointer

Instead of duplicating a user's entire profile inside every order, the order just stores a pointer — user_id — back to where that user's full information actually lives.

Primary Key
The value that uniquely identifies one row in a table; never empty, never duplicated.
Foreign Key
A column that stores another table's primary key value, creating a link between two rows.
Surrogate Key
A value generated purely for identification (an auto-incrementing integer or UUID), with no meaning outside the database.
text
FOREIGN KEY LINK: ORDERS TO USERS
---------------------------------
FOREIGN KEY LINK: ORDERS TO USERS
------------------------------------
   users                    orders
+----+-------+     +-----+---------+----------+
| id | name  |      | id  | user_id | item     |
+----+-------+     +-----+---------+----------+
| 1  | Nilar |      | 101 |    2    | Keyboard |
| 2  | Zaw   |      | 102 |    1    | Monitor  |
| 3  | Hnin  |      | 103 |    3    | Headset  |
+----+-------+      +-----+---------+----------+

  users.id (primary key)  <-  orders.user_id (foreign key)

Connect it to a real scenario

When modeling any new feature, ask two separate questions: 'what uniquely identifies one row here?' (a primary key candidate) and 'does this row need to point back to a row somewhere else?' (a foreign key candidate).

A frequent real-world tension: use an email as a primary key, or generate a surrogate id instead? Many production systems choose the surrogate id so a later email change doesn't ripple into every referencing table.

The lookup pattern

Given an order, following its user_id to find the matching row in users is the pattern behind the SQL course's real JOIN syntax. Recognizing it by eye helps you read an unfamiliar schema fast.

Try the working example

javascript
const users = [
  { id: 1, name: "Nilar" },
  { id: 2, name: "Zaw" },
  { id: 3, name: "Hnin" },
];

const orders = [
  { id: 101, user_id: 2, item: "Keyboard" },
  { id: 102, user_id: 1, item: "Monitor" },
  { id: 103, user_id: 3, item: "Headset" },
];

function findOrderOwner(orderId, ordersTable, usersTable) {
  const order = ordersTable.find((o) => o.id === orderId);
  if (!order) return null;
  const user = usersTable.find((u) => u.id === order.user_id);
  return user ? { item: order.item, owner: user.name } : null;
}

console.log(findOrderOwner(101, orders, users));
console.log(findOrderOwner(103, orders, users));
console.log(findOrderOwner(999, orders, users));
You should see
{ item: 'Keyboard', owner: 'Zaw' }
{ item: 'Headset', owner: 'Hnin' }
null

5-minute try-it

Add a new order with a user_id that doesn't match any user to the orders array, run findOrderOwner on it, predict the result first, then check.

One important caution

Assuming a foreign key value always matches an existing row — without a real constraint enforcing it, 'orphaned' foreign keys pointing at deleted rows are a common real bug

Picking a natural key like email as a primary key and then hard-coding that assumption everywhere, making it painful when that value legitimately needs to change

Wikipedia: Foreign keyHow Databases Work

Easy traps

  • Assuming a foreign key value always matches an existing row — without a real constraint enforcing it, 'orphaned' foreign keys pointing at deleted rows are a common real bug
  • Picking a natural key like email as a primary key and then hard-coding that assumption everywhere, making it painful when that value legitimately needs to change
  • This course teaches database concepts and the product landscape at a framework-neutral level -- for hands-on SQL syntax or PostgreSQL/MongoDB/Redis depth, continue to the SQL, PostgreSQL, MongoDB, or Redis tutorials.

Exercise

Add a new order with a user_id that doesn't match any user to the orders array, run findOrderOwner on it, predict the result first, then check.

You'll know it worked when: { item: 'Keyboard', owner: 'Zaw' } { item: 'Headset', owner: 'Hnin' } null