Does SQLite reuse a deleted id, with and without
AUTOINCREMENT?SQLCREATE TABLE plain (id INTEGER PRIMARY KEY, v TEXT); CREATE TABLE auto (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT); INSERT INTO plain (v) VALUES ('a'), ('b'), ('c'); INSERT INTO auto (v) VALUES ('a'), ('b'), ('c'); DELETE FROM plain WHERE id = 3; DELETE FROM auto WHERE id = 3; INSERT INTO plain (v) VALUES ('d'); INSERT INTO auto (v) VALUES ('d'); SELECT (SELECT max(id) FROM plain), (SELECT max(id) FROM auto);It prints
3 | 4. WithoutAUTOINCREMENT, SQLite assignsmax(rowid) + 1, so id 3 is reused; with it, SQLite records the largest id ever used insqlite_sequence(SQLite AUTOINCREMENT docs). In general, ids are unique but not gapless: sequences andAUTO_INCREMENTleave gaps on rollback, and before MySQL 8.0 InnoDB could reuse a deleted top id after a restart.What happens when a payment with the same idempotency key is retried?
SQLCREATE TABLE payments ( id INTEGER PRIMARY KEY, idempotency_key TEXT UNIQUE NOT NULL, amount INTEGER ); INSERT INTO payments (idempotency_key, amount) VALUES ('req-7f3a', 500) ON CONFLICT (idempotency_key) DO NOTHING; INSERT INTO payments (idempotency_key, amount) VALUES ('req-7f3a', 500) ON CONFLICT (idempotency_key) DO NOTHING; SELECT count(*), sum(amount) FROM payments;It prints
1 | 500. The retry hits the uniqueidempotency_key, andON CONFLICT DO NOTHINGmakes it a no-op, so the payment is stored once however often the client retries. The constraint is what guarantees it; a check-then-insert in the app races. See upsert.Why is OFFSET pagination slow on large tables, and what is the alternative?
OFFSET 100000reads and discards 100,000 rows first, so deep pages get slower. Keyset pagination continues after the previous page's last row with an indexedWHERE, so every page costs the same:SQLCREATE TABLE posts (id INTEGER PRIMARY KEY, created_at TEXT, title TEXT); CREATE INDEX idx_posts_created ON posts(created_at, id); INSERT INTO posts (created_at, title) VALUES ('2026-10-01', 'a'), ('2026-10-02', 'b'), ('2026-10-02', 'c'), ('2026-10-03', 'd'), ('2026-10-04', 'e'); -- the previous page ended at ('2026-10-02', 3); fetch the next 2 SELECT id, created_at, title FROM posts WHERE (created_at, id) > ('2026-10-02', 3) ORDER BY created_at, id LIMIT 2;Order by a unique combination (
created_at, id), or rows with equal timestamps get skipped or repeated. You give up jumping straight to page 50.What join algorithms does a database use, and when does it pick each?
Nested loop, hash join and sort-merge, chosen from row estimates, indexes and memory. A nested loop looks up inner matches for each outer row, ideal for a small outer side and an indexed join column. A hash join builds a hash table on the smaller input and probes it, O(n + m), for large equality joins without an index (MySQL only since 8.0.18, see the MySQL hash join docs). A sort-merge join walks two inputs sorted on the key, best when they arrive sorted. A wrong pick usually comes from a bad row estimate.
Practice it: Intersection of Two Arrays, Merge Sorted Array
A query is slow in production. How do you find and fix the cause?
Measure, read the plan, fix the expensive step. Find the query with the slow query log or
pg_stat_statements, ranked by total time. InEXPLAIN ANALYZE, look for full scans of big tables, sorts spilling to disk, and estimates far from actual rows. Usual causes: a missing index, a function on an indexed column, stale statistics, N+1 queries or lock waits.SQLCREATE TABLE customers (id INTEGER PRIMARY KEY, city TEXT); CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, total INTEGER); CREATE INDEX idx_orders_customer ON orders(customer_id); EXPLAIN QUERY PLAN SELECT c.city, sum(o.total) FROM customers c JOIN orders o ON o.customer_id = c.id WHERE c.city = 'Pune' GROUP BY c.city;SCAN creads every customer to filter by city, thenSEARCH ouses the index; an index oncustomers(city)removes the scan. See EXPLAIN QUERY PLAN.What is the N+1 query problem, and how do you fix it?
N+1 is one query for a list plus one per item for related data: 1 query for 50 orders, then 50 for their customers. ORM lazy loading in a loop usually causes it. Fetch the related rows in one query with a join or
IN:SQLCREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, total INTEGER); INSERT INTO customers VALUES (1, 'Asha'), (2, 'Ravi'); INSERT INTO orders VALUES (10, 1, 500), (11, 2, 300), (12, 1, 200); -- one query instead of 1 + N SELECT o.id, o.total, c.name FROM orders o JOIN customers c ON c.id = o.customer_id ORDER BY o.id;ORMs have it built in: Django
select_related, JPAJOIN FETCH, Railsincludes. A query count that grows with page size gives it away.What is SQL injection, and how do you prevent it?
SQL injection is user input pasted into SQL text, where it can change the statement. Parameterized queries fix it: the SQL and the values travel separately, so a value never becomes code.
Pythonimport sqlite3 db = sqlite3.connect(":memory:") db.execute("CREATE TABLE users (name TEXT, password TEXT)") db.executemany("INSERT INTO users VALUES (?, ?)", [("asha", "s3cret"), ("ravi", "hunter2")]) name = "nobody" password = "' OR '1'='1" unsafe = f"SELECT name FROM users WHERE name = '{name}' AND password = '{password}'" print("string building:", db.execute(unsafe).fetchall()) safe = "SELECT name FROM users WHERE name = ? AND password = ?" print("parameters: ", db.execute(safe, (name, password)).fetchall())The built string ends
password = '' OR '1'='1', and sinceANDbinds tighter thanOR, every user matches; the parameterized query matches nothing. Table and column names cannot be parameters, so check them against an allow list. See preventing SQL injection.What is the difference between a stored procedure, a function and a trigger?
A stored procedure is named code you call (
CALL,EXEC) that can change data and manage transactions; a function returns a value inside queries; a trigger runs automatically onINSERT,UPDATEorDELETE. SQLite has only triggers:SQLCREATE TABLE salaries (emp TEXT PRIMARY KEY, amount INTEGER); CREATE TABLE salary_audit (emp TEXT, old_amount INTEGER, new_amount INTEGER); CREATE TRIGGER log_salary_change AFTER UPDATE OF amount ON salaries BEGIN INSERT INTO salary_audit VALUES (OLD.emp, OLD.amount, NEW.amount); END; INSERT INTO salaries VALUES ('Asha', 50000); UPDATE salaries SET amount = 55000 WHERE emp = 'Asha'; SELECT emp, old_amount, new_amount FROM salary_audit;Keep triggers small, like audit logs, since logic outside the application code is easy to miss when debugging. See triggers.
What is replication lag, and how do you deal with read-your-own-writes problems?
Replication lag is the delay before a primary's commit shows on a replica. With asynchronous replication, the MySQL and PostgreSQL default, a user who saves a profile and reads from a replica may see the old one. Fixes: send a user's reads to the primary briefly after they write, pin a user to one replica so data never goes backwards, or read only from a replica past the write's log position (LSN, GTID).
How do you change a database schema without downtime?
Expand and contract: each deploy is backward compatible, so old and new code both work. To rename
fullnametodisplay_name: add the new column, write both, backfill in small batches, switch reads, stop writing the old column, then drop it in a later deploy. Traps: someALTER TABLEforms rewrite or lock the table, so check your engine version or usegh-ost; in PostgreSQL, build indexes withCREATE INDEX CONCURRENTLYand set a shortlock_timeoutso a blockedALTERfails fast instead of queuing every query behind it.Should the primary key be an auto-increment integer or a UUID?
Auto-increment integers are 4 or 8 bytes and insert at the end of the index, the fastest choice on one database; UUIDs can be generated anywhere and do not leak row counts. Random UUIDv4 keys hurt clustered indexes (InnoDB, SQL Server): inserts land on random pages, causing splits and cache misses. UUIDv7 (RFC 9562) puts a timestamp first, so inserts are nearly sequential, though it reveals creation time. A common compromise is a
BIGINTprimary key plus a random public id.
Advanced DBMS Interview Questions
Key generation, idempotent writes, pagination, join algorithms, query tuning, replication lag, SQL injection and migrations.