Why interviewers love this: it sounds simple, but the real answer tests whether you understand transactions, snapshots, and hidden concurrency bugs.
Question: What is Repeatable Read in SQL?
Answer: Repeatable Read is a transaction isolation level that gives one transaction a stable view of the data. If I read the same row twice before I commit, I should see the same value both times, even if another transaction commits a change in the middle. In plain words: once my transaction takes its snapshot, it keeps looking at that same snapshot until it ends.
Interview-Ready Answer: I’d say Repeatable Read means every read inside my transaction sees the same committed snapshot, so a row I read once will not appear to change when I read it again. That prevents non-repeatable reads and makes multi-step logic easier to reason about. The important nuance is that it is still weaker than Serializable, so some business-rule anomalies like write skew can still slip through depending on the database engine.
Detailed Explanation:
Repeatable Read is about keeping your reads stable inside one transaction. A non-repeatable read happens when you read a row, someone else commits an update, and then you read that same row again and get a different value. Repeatable Read is designed to stop that surprise.
| Level | Same row reread | Main weakness | Typical default |
|---|---|---|---|
| Read Committed | Can change | Non-repeatable reads | PostgreSQL |
| Repeatable Read | Stays stable | Write skew, engine differences | MySQL InnoDB |
| Serializable | Stays stable | Lowest concurrency, more retries | Rarely default |
That table is the big interview clue: Repeatable Read is stronger than Read Committed, but weaker than Serializable. Also, engine behavior is not identical. PostgreSQL’s Repeatable Read is snapshot-based and very stable for reads, while other systems may use locks differently for locking reads. So always think “what does this database mean by the label?”
Big-O is not the best lens here, but reads in an MVCC engine are usually close to O(1) to find the visible version. The hidden cost is storage and cleanup: long-running transactions can pin old row versions, so the database must keep more history around. On a busy table, that can mean anything from a few extra megabytes to gigabytes of old versions if a transaction stays open for minutes or hours.
Common gotcha: Repeatable Read stops your view from changing, but it does not magically make every business rule safe. Two transactions can still each see a valid snapshot and make decisions that conflict together later. That is why interviewers often pair this topic with write skew, phantom behavior, and Serializable isolation.
Version note: PostgreSQL defaults to Read Committed, MySQL InnoDB defaults to Repeatable Read, and SQL Server commonly defaults to Read Committed. Same name, different defaults, and sometimes different locking behavior. That is exactly why you should describe the guarantee, not just memorize the label.
Real-World Story: Imagine a checkout service for an e-commerce app. A customer opens the cart, the service checks item prices, shipping, and available stock, and then calculates the final charge. If the transaction uses Repeatable Read, the cart total stays consistent through the whole checkout flow, so the user does not see the price change halfway through payment.
What goes wrong when people misunderstand this? A team uses Read Committed for the checkout read path and assumes it is “good enough.” Mid-checkout, a promo job updates pricing, so the first read shows one subtotal and the second read shows another. Users report “my total changed before I paid,” payment logs show mismatched authorization amounts, and support sees complaints like subtotal_changed_during_checkout or cart_total_mismatch. The bug is not just annoying; it can cause failed charges, duplicate retries, and reconciliation headaches between the order service and the payment gateway.
-- Repeatable Read demo for PostgreSQL.
-- Run the Session A and Session B blocks in two separate connections.
-- The point is to show that Session A keeps seeing the same row version.
DROP TABLE IF EXISTS account_balance;
CREATE TABLE account_balance (
id INT PRIMARY KEY,
owner TEXT NOT NULL,
balance INT NOT NULL CHECK (balance >= 0)
);
INSERT INTO account_balance (id, owner, balance)
VALUES (1, 'Ava', 100);
-- =========================
-- Session A: first terminal
-- =========================
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- This read takes a snapshot. Later reads in this transaction use the same snapshot.
SELECT balance AS session_a_first_read
FROM account_balance
WHERE id = 1;
-- Now switch to Session B and run its block before coming back here.
SELECT balance AS session_a_second_read
FROM account_balance
WHERE id = 1;
-- Even if Session B committed a change, PostgreSQL keeps Session A's view stable.
-- If Session A tries to update a row that changed after the snapshot, the engine may reject it.
-- That is the safety net that prevents stale writes from silently winning.
-- UPDATE account_balance SET balance = balance - 30 WHERE id = 1;
ROLLBACK;
-- =========================
-- Session B: second terminal
-- =========================
BEGIN;
UPDATE account_balance
SET balance = balance - 30
WHERE id = 1;
COMMIT;
-- Final state after both sessions finish.
SELECT *
FROM account_balance
ORDER BY id;Follow-up & Tricky Questions:
Tricky point: many candidates say Repeatable Read is “just like Serializable but weaker.” That is directionally right, but incomplete. The real interview-grade answer is that it guarantees a stable read view, yet some cross-row anomalies can still happen unless the engine uses stronger locking rules or you move to Serializable.
Common Mistakes:
Memory Hook: Repeatable Read is like taking a photo of the database when the transaction starts. Every reread is you looking at the same photo, not the live scene.
Cheat Sheet:
Practice Tasks: