A foreign key is the database version of a bouncer: it only lets in child rows that belong to a real parent row.
Question: What is a foreign key in SQL?
Answer: A foreign key is a column, or group of columns, in one table that points to a primary key or unique key in another table. Its job is to protect referential integrity, which means the database does not allow a child row to refer to a missing parent row. It can also control what happens when the parent row is updated or deleted.
Interview-Ready Answer: I think of a foreign key as the rule that keeps two tables consistent. The child table stores a value that must match an existing parent key, unless the column allows NULL. In practice, I use foreign keys to prevent orphan rows and to define clear delete or update behavior, such as CASCADE or RESTRICT.
A foreign key is not just a label; it is an enforceable rule. The parent table usually holds the master record, like customers, and the child table stores related rows, like orders. The child row is only valid if its foreign key value points to a real parent row.
FOREIGN KEY (customer_id) REFERENCES customers(customer_id).INSERT or UPDATE the child row, the database checks whether the referenced parent key exists.DELETE or UPDATE the parent row, the database looks for matching child rows and applies the chosen action, such as CASCADE, SET NULL, or blocking the change.ON DELETE CASCADE only when child rows truly should disappear with the parent, such as temporary session data.| Constraint | What it protects | Typical use |
|---|---|---|
| Primary key | Row identity | One unique row per table |
| Foreign key | Cross-table link | Parent-child relationship |
| Unique | Duplicate values | Email, username, code |
A primary key says, this row is the one and only record in this table. A foreign key says, this row must point to a real record over there. A unique constraint says, do not repeat this value in the same table.
Foreign key checks are cheap when the referenced parent key is indexed, which it usually is because primary keys and unique keys are indexed. The extra cost shows up on writes, not reads: inserts, updates, and deletes have to validate the relationship. If the child foreign key column is not indexed, deleting a parent with millions of children can force a large scan of the child table; that can turn a fast change into seconds or even minutes on busy systems. A good rule is to index the child foreign key column when the parent row is frequently updated or deleted, especially in large tables.
manager_id.NO ACTION or a very similar rule, so the parent change is blocked if children exist.Memory-level takeaway: a foreign key is the database saying, show me the parent first, then I will accept the child.
Imagine an e-commerce checkout service with customers and orders. Every order must belong to a real customer, so the orders.customer_id column is protected by a foreign key. That means the system can trust every order record, every invoice join, and every support lookup.
Now the bug story: a team once imported order history before loading customer records. If the foreign key was enabled, the import failed fast with a clear constraint error, which was annoying but safe. If the team disabled the constraint to get the load through, they created orphan orders. The next morning, customer support saw orders with blank customer names, finance reports undercounted revenue because joins dropped missing parents, and ETL jobs logged mismatch errors. One missing rule turned a clean data model into a messy cleanup project.
What goes wrong: symptoms usually include constraint violation errors during writes, missing rows in joins, weird NULLs in reports, and users asking why their order or profile cannot be found. The root cause is almost always broken referential integrity, either because the foreign key was missing, disabled, or misconfigured.
-- Demonstration of a foreign key with a safe delete action and an invalid insert path.
-- This script is intentionally simple and self-contained.
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
full_name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_total DECIMAL(10,2) NOT NULL,
CONSTRAINT fk_orders_customers
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO customers (customer_id, full_name) VALUES
(1, 'Ava Patel'),
(2, 'Ben Lee');
INSERT INTO orders (order_id, customer_id, order_total) VALUES
(101, 1, 49.99),
(102, 2, 19.95);
SELECT 'Before delete' AS phase, order_id, customer_id, order_total
FROM orders
ORDER BY order_id;
-- Edge case / failure path: this would fail because customer_id = 999 has no matching parent row.
-- The database rejects the write to prevent an orphan order.
-- INSERT INTO orders (order_id, customer_id, order_total) VALUES (103, 999, 12.00);
-- Because ON DELETE CASCADE is set, removing a customer removes that customer's orders too.
DELETE FROM customers WHERE customer_id = 1;
SELECT 'After cascade' AS phase, order_id, customer_id, order_total
FROM orders
ORDER BY order_id;
-- ON UPDATE CASCADE keeps child rows aligned if the parent key changes.
UPDATE customers
SET customer_id = 20
WHERE customer_id = 2;
SELECT 'After update cascade' AS phase, order_id, customer_id, order_total
FROM orders
ORDER BY order_id;Follow-up & Tricky Questions:
ON DELETE CASCADE do? It deletes child rows automatically when the parent row is deleted, which is powerful but dangerous if you did not intend to remove history.RESTRICT and SET NULL? RESTRICT blocks the parent delete or update, while SET NULL keeps the child row but clears the foreign key value, which requires the column to allow NULL.CASCADE it deletes children, with SET NULL it clears them, and with RESTRICT or NO ACTION it is blocked.Common Mistakes:
CASCADE too casually. Correction: cascade is great for true dependent data, but it can delete more rows than you expect if the relationship is misunderstood.Memory Hook: think of the parent as the office that stamps passports, and the child as the traveler. No valid stamp, no entry.
Cheat Sheet:
CASCADE, SET NULL, RESTRICT, NO ACTION.Practice Tasks:
authors and books, then add a foreign key from books.author_id to authors.author_id.SET NULL or CASCADE and see how deletes behave differently.