Top 60 PostgreSQL Interview Questions (2026)

The 60 PostgreSQL questions interviewers actually ask, with direct answers, runnable SQL, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is PostgreSQL?

Key Takeaways

  • PostgreSQL is a free, open-source object-relational database that speaks SQL and adds rich types, extensions, and full ACID guarantees.
  • It uses MVCC so readers don't block writers and writers don't block readers, which shapes a lot of its interview questions.
  • Interviews test how well you understand its concurrency model, indexing, query planning, and JSON support, not just SELECT syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery is evaluated too.

PostgreSQL is a free, open-source object-relational database management system with more than 35 years of active development behind it. It's fully ACID compliant, handles heavy concurrent workloads through Multi-Version Concurrency Control (MVCC), and extends standard SQL with types like JSONB, arrays, and ranges, plus an extension system that adds things like PostGIS for geospatial data. In interviews, PostgreSQL questions probe how the engine behaves under concurrency (MVCC, isolation levels, locks), how the planner chooses between a sequential scan and an index, and how you model and query real data, not memorized keywords. This page collects the 60 questions that come up most, each with a direct answer and runnable SQL. If you're still building fundamentals, the official PostgreSQL documentation from the PostgreSQL Global Development Group is the canonical reference; increasingly the first technical round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
24Runnable SQL snippets you can practice from
45-60 minTypical length of a PostgreSQL technical round

Watch: PostgreSQL in 100 Seconds

Video: PostgreSQL in 100 Seconds (Fireship, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your PostgreSQL certificate.

Jump to quiz

All Questions on This Page

60 questions
PostgreSQL Interview Questions for Freshers
  1. 1. What is PostgreSQL and what makes it stand out?
  2. 2. What is a relational database and what is a table, row, and column?
  3. 3. What is a primary key?
  4. 4. What is a foreign key and what does it enforce?
  5. 5. What is the difference between SQL and PostgreSQL?
  6. 6. What are DDL, DML, DCL, and TCL commands?
  7. 7. What is the difference between DELETE, TRUNCATE, and DROP?
  8. 8. What are the types of JOINs in PostgreSQL?
  9. 9. What is the difference between WHERE and HAVING?
  10. 10. What does GROUP BY do, and what are aggregate functions?
  11. 11. How does NULL behave in PostgreSQL?
  12. 12. What does DISTINCT do, and what is DISTINCT ON?
  13. 13. How do ORDER BY, LIMIT, and OFFSET work?
  14. 14. What are the common data types in PostgreSQL?
  15. 15. What is the difference between VARCHAR, CHAR, and TEXT?
  16. 16. How do you insert, update, and delete data in PostgreSQL?
  17. 17. How do you do an upsert (insert or update) in PostgreSQL?
  18. 18. What are constraints in PostgreSQL?
  19. 19. What is an index and why does it speed up queries?
  20. 20. What is the difference between aggregate and scalar functions?
  21. 21. What is psql and what are a few useful meta-commands?
  22. 22. What is a transaction in PostgreSQL?
  23. 23. What is the difference between COUNT(*), COUNT(column), and COUNT(DISTINCT column)?
  24. 24. What is the difference between SERIAL and GENERATED AS IDENTITY?
PostgreSQL Intermediate Interview Questions
  1. 25. What is MVCC and how does PostgreSQL implement it?
  2. 26. What are VACUUM and autovacuum, and why do they matter?
  3. 27. What index types does PostgreSQL offer, and when do you use each?
  4. 28. How do you read EXPLAIN and EXPLAIN ANALYZE output?
  5. 29. What transaction isolation levels does PostgreSQL support?
  6. 30. What are the ACID properties and how does PostgreSQL guarantee them?
  7. 31. When would you use a subquery versus a JOIN?
  8. 32. What is a CTE (WITH clause) and when is it useful?
  9. 33. What are window functions and how do they differ from GROUP BY?
  10. 34. How does PostgreSQL handle JSON, and what is the difference between JSON and JSONB?
  11. 35. What is normalization, and what are the first three normal forms?
  12. 36. When would you denormalize, and what are the trade-offs?
  13. 37. What is the difference between a view and a materialized view?
  14. 38. What is the difference between a function and a procedure in PostgreSQL?
  15. 39. What are triggers and when should you use them?
  16. 40. What is a sequence and how do currval and nextval work?
  17. 41. How does the CASE expression work?
  18. 42. Why do you need connection pooling with PostgreSQL?
  19. 43. Why use NUMERIC instead of FLOAT for money, and how do string functions help clean data?
  20. 44. How do you back up and restore a PostgreSQL database?
PostgreSQL Interview Questions for Experienced Engineers
  1. 45. What is the write-ahead log (WAL) and what does it do?
  2. 46. What kinds of locks does PostgreSQL use, and what is SELECT FOR UPDATE?
  3. 47. What causes a deadlock and how does PostgreSQL handle it?
  4. 48. How does the PostgreSQL query planner decide on a plan?
  5. 49. What is table partitioning and when should you use it?
  6. 50. How does replication work in PostgreSQL?
  7. 51. A query is slow in production. Walk through how you diagnose it.
  8. 52. What is an index-only scan, and what is a covering index?
  9. 53. What are partial and expression indexes?
  10. 54. Why and how do you build an index without blocking writes?
  11. 55. What is transaction ID wraparound and why does it matter?
  12. 56. What is TOAST in PostgreSQL?
  13. 57. What are HOT updates and how does fillfactor help?
  14. 58. What are extensions, and which ones come up often?
  15. 59. Should you use UUID or BIGINT for primary keys?
  16. 60. How would you design a highly available PostgreSQL setup?

PostgreSQL Interview Questions for Freshers

Freshers24 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is PostgreSQL and what makes it stand out?

PostgreSQL is a free, open-source object-relational database that speaks standard SQL and adds features like rich data types, extensions, and full ACID transactions. It's known for correctness and for handling both relational and JSON data in one engine.

What sets it apart is depth in the core: MVCC concurrency, window functions, CTEs, JSONB, full-text search, and an extension system (like PostGIS). Teams reach for it when they want one dependable database rather than several specialized ones.

Key point: A one-line definition plus two concrete features (MVCC, JSONB) beats a memorized feature list. Interviewers open with this to hear how you organize an answer.

Watch a deeper explanation

Video: Learn PostgreSQL Tutorial - Full Course for Beginners (freeCodeCamp.org, YouTube)

Q2. What is a relational database and what is a table, row, and column?

A relational database stores data in tables made of rows and columns, where each table represents one kind of thing and relationships between tables are expressed with keys. A row is a single record, a column is one attribute, and every column has a defined data type.

The 'relational' part is the point: instead of duplicating data everywhere, you link tables with foreign keys and join them when you query. That keeps data consistent and storage lean.

Q3. What is a primary key?

A primary key uniquely identifies every row in a table. It's both UNIQUE and NOT NULL, there's at most one per table, and PostgreSQL automatically builds an index on it so lookups are fast.

A primary key can be a single column or several columns together (a composite key). Many teams use a generated identity column or a UUID as a surrogate key rather than relying on natural data.

sql
CREATE TABLE employees (
    id   BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name TEXT NOT NULL,
    team TEXT
);

Key point: The follow-up is often 'natural key or surrogate key?'. Have a one-line reason ready: surrogate keys stay stable when business data changes.

Q4. What is a foreign key and what does it enforce?

A foreign key is a column (or set of columns) whose values must match a key in another table. It enforces referential integrity: you can't insert an order for a customer that doesn't exist, and you control what happens on delete or update.

The ON DELETE and ON UPDATE clauses set that behavior: CASCADE removes dependent rows, RESTRICT blocks the delete, and SET NULL clears the reference.

sql
CREATE TABLE orders (
    id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer_id BIGINT NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    total       NUMERIC(10,2) NOT NULL
);

Q5. What is the difference between SQL and PostgreSQL?

SQL is the standard query language for relational databases: SELECT, INSERT, UPDATE, DELETE, and the rest. PostgreSQL is a database system that implements SQL and extends it with its own features.

So you write SQL to talk to PostgreSQL. Most SQL you learn is portable, but each database adds extensions: PostgreSQL has JSONB operators, array types, and its own procedural language, PL/pgSQL, that aren't part of the standard.

Q6. What are DDL, DML, DCL, and TCL commands?

They group SQL by purpose. DDL defines structure (CREATE, ALTER, DROP). DML changes data (SELECT, INSERT, UPDATE, DELETE). DCL controls access (GRANT, REVOKE). TCL manages transactions (BEGIN, COMMIT, ROLLBACK, SAVEPOINT).

CategoryPurposeExamples
DDLDefine schemaCREATE, ALTER, DROP, TRUNCATE
DMLWork with dataSELECT, INSERT, UPDATE, DELETE
DCLControl accessGRANT, REVOKE
TCLControl transactionsBEGIN, COMMIT, ROLLBACK, SAVEPOINT

Key point: Knowing which bucket TRUNCATE falls in (DDL, not DML) is a common gotcha. That's why TRUNCATE can't always be rolled back the way DELETE can.

Q7. What is the difference between DELETE, TRUNCATE, and DROP?

DELETE removes rows and accepts a WHERE filter, logging each row so it's fully transactional. TRUNCATE empties an entire table quickly by deallocating data pages instead of scanning rows. DROP removes the table definition itself, structure and all.

In PostgreSQL, TRUNCATE is transactional and can be rolled back, but it takes a stronger lock and doesn't fire per-row triggers. Reach for DELETE when you need a filter, TRUNCATE to clear a whole table fast, DROP to get rid of the table.

sql
DELETE FROM logs WHERE created_at < '2026-01-01';  -- filtered, per-row
TRUNCATE TABLE logs;                                -- empties the whole table fast
DROP TABLE logs;                                   -- removes the table entirely

Q8. What are the types of JOINs in PostgreSQL?

INNER JOIN returns only rows that match in both tables. LEFT JOIN keeps all left rows plus matches. RIGHT JOIN keeps all right rows plus matches. FULL OUTER JOIN keeps everything from both sides. CROSS JOIN pairs every left row with every right row.

The one people forget is the NULL behavior: an outer join fills the non-matching side with NULLs, which is exactly how you find rows that have no match (a WHERE ... IS NULL after a LEFT JOIN).

sql
-- customers with no orders yet
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
JoinKeepsFills non-matches with
INNEROnly matching rowsNothing (drops them)
LEFTAll left rowsNULLs on the right
RIGHTAll right rowsNULLs on the left
FULL OUTERAll rows, both sidesNULLs on either side

Key point: The classic follow-up is 'how do you find rows with no match?'. The LEFT JOIN plus IS NULL trick is the answer they want.

Watch a deeper explanation

Video: SQL Joins Basics (Visually Explained) | INNER, LEFT, RIGHT, FULL (Data with Baraa, YouTube)

Q9. What is the difference between WHERE and HAVING?

WHERE filters individual rows before grouping. HAVING filters groups after GROUP BY has aggregated them. So you use WHERE for row conditions and HAVING for conditions on aggregates like COUNT or SUM.

You can't put an aggregate in a WHERE clause; that's what HAVING is for. A query can use both: WHERE trims rows first, then HAVING trims the resulting groups.

sql
SELECT team, COUNT(*) AS headcount
FROM employees
WHERE active = true          -- filters rows first
GROUP BY team
HAVING COUNT(*) > 5;         -- filters groups after aggregation

Q10. What does GROUP BY do, and what are aggregate functions?

GROUP BY collapses rows that share the same values into one row per group, so you can summarize. Aggregate functions like COUNT, SUM, AVG, MIN, and MAX compute one value per group.

Every column in the SELECT list either appears in GROUP BY or is wrapped in an aggregate. Miss that and Postgres raises an error telling you the column must appear in the GROUP BY.

sql
SELECT team, AVG(salary) AS avg_salary, COUNT(*) AS n
FROM employees
GROUP BY team
ORDER BY avg_salary DESC;

Q11. How does NULL behave in PostgreSQL?

NULL means 'unknown', not zero or empty string. Any comparison with NULL using = or <> returns NULL (treated as not true), which is why you test it with IS NULL and IS NOT NULL.

NULL also spreads: NULL + 5 is NULL, and most aggregates skip NULLs (COUNT(column) ignores them while COUNT(*) doesn't). Use COALESCE to substitute a fallback value.

sql
SELECT name, COALESCE(phone, 'no phone') AS phone
FROM contacts
WHERE email IS NOT NULL;   -- never use = NULL

Key point: Interviewers love 'why doesn't WHERE x = NULL work?'. Answer: NULL means unknown, so the comparison is never true; use IS NULL.

Q12. What does DISTINCT do, and what is DISTINCT ON?

DISTINCT removes duplicate rows from a result set. DISTINCT ON is a PostgreSQL feature that keeps the first row for each group of the listed columns, which is handy for 'latest row per key' queries.

DISTINCT ON pairs with ORDER BY: the first row per group is decided by the sort order, so you order by the key then by whatever picks the winner.

sql
-- latest login per user
SELECT DISTINCT ON (user_id) user_id, logged_in_at
FROM logins
ORDER BY user_id, logged_in_at DESC;

Q13. How do ORDER BY, LIMIT, and OFFSET work?

ORDER BY sorts the result, ascending by default or descending with DESC. LIMIT caps how many rows come back, and OFFSET skips a number of rows first, which together give basic pagination.

One caution: OFFSET on large tables gets slow because the database still scans and discards the skipped rows. Keyset pagination (WHERE id > last_seen_id) scales better.

sql
SELECT id, title
FROM articles
ORDER BY published_at DESC
LIMIT 10 OFFSET 20;   -- page 3 of 10-per-page

Q14. What are the common data types in PostgreSQL?

Numbers: INTEGER, BIGINT, NUMERIC (exact decimals for money), and REAL/DOUBLE PRECISION (floating point). Text: TEXT and VARCHAR(n). Dates and times: DATE, TIMESTAMP, TIMESTAMPTZ. Plus BOOLEAN, UUID, JSONB, arrays, and more.

A practical tip that shows judgment: use NUMERIC for money to avoid floating-point rounding, and prefer TIMESTAMPTZ over TIMESTAMP so time zones are handled correctly.

NeedTypeWhy
MoneyNUMERIC(p,s)Exact decimals, no float rounding
Whole numbersINTEGER / BIGINTBIGINT when values exceed ~2 billion
TimestampsTIMESTAMPTZStores time zone context correctly
Flexible JSONJSONBBinary, indexable, queryable

Q15. What is the difference between VARCHAR, CHAR, and TEXT?

TEXT stores strings of any length. VARCHAR(n) is TEXT with a length limit. CHAR(n) is fixed-length and pads short values with spaces, which is rarely what you want.

In PostgreSQL there's no performance penalty for TEXT over VARCHAR; the only difference is the length check. Many teams just use TEXT and enforce limits with a CHECK constraint when they need one.

Q16. How do you insert, update, and delete data in PostgreSQL?

INSERT adds rows, UPDATE changes existing rows, and DELETE removes them. All three accept a WHERE clause where it makes sense, and all three support RETURNING to hand back the affected rows in the same statement.

Forgetting WHERE on an UPDATE or DELETE changes or removes every row. Running inside a transaction (BEGIN ... COMMIT) gives you a safety net to roll back a mistake.

sql
INSERT INTO employees (name, team) VALUES ('Asha', 'platform')
RETURNING id;

UPDATE employees SET team = 'infra' WHERE id = 42;

DELETE FROM employees WHERE id = 42;

Key point: The RETURNING clause is a strong thing to volunteer here: it avoids a second SELECT to fetch the generated id.

Q17. How do you do an upsert (insert or update) in PostgreSQL?

Use INSERT ... ON CONFLICT. When a row would violate a unique or primary key constraint, you tell Postgres what to do instead: DO NOTHING to skip it, or DO UPDATE to merge in the new values.

This is atomic and avoids the race condition of 'check then insert' from application code. EXCLUDED refers to the row you tried to insert.

sql
INSERT INTO settings (user_id, theme)
VALUES (7, 'dark')
ON CONFLICT (user_id)
DO UPDATE SET theme = EXCLUDED.theme;

Q18. What are constraints in PostgreSQL?

Constraints are rules the database enforces on your data: NOT NULL (no missing values), UNIQUE (no duplicates), PRIMARY KEY (unique and not null), FOREIGN KEY (must reference a valid row), CHECK (a custom condition), and DEFAULT (a fallback value).

Putting rules in constraints instead of application code means the database guarantees them for every write, no matter which app or script does the inserting.

sql
CREATE TABLE products (
    id     BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    sku    TEXT UNIQUE NOT NULL,
    price  NUMERIC(10,2) NOT NULL CHECK (price >= 0),
    active BOOLEAN NOT NULL DEFAULT true
);

Q19. What is an index and why does it speed up queries?

An index is a separate data structure that lets PostgreSQL find rows without scanning the whole table, the way a book's index sends you straight to a page. The default is a B-tree, which handles equality and range lookups on ordered columns.

Indexes speed up reads but slow down writes, because every INSERT, UPDATE, or DELETE must also update the index, and they take disk space. So you index columns you filter and join on, not every column.

sql
CREATE INDEX idx_orders_customer ON orders (customer_id);
-- now WHERE customer_id = ? can use the index instead of a full scan

Key point: The trade-off answer (faster reads, slower writes, more disk) is what separates 'add indexes to everything' from real understanding.

Watch a deeper explanation

Video: what is a database index? (Hussein Nasser, YouTube)

Q20. What is the difference between aggregate and scalar functions?

A scalar function returns one value per row: UPPER(name), LENGTH(text), ROUND(price). An aggregate function returns one value per group of rows: COUNT, SUM, AVG, MIN, MAX.

You mix them, but only aggregates trigger grouping. If a query has an aggregate and other columns, those columns must go in GROUP BY.

Q21. What is psql and what are a few useful meta-commands?

psql is PostgreSQL's interactive terminal client. Beyond running SQL, it has backslash meta-commands for exploring the database: \dt lists tables, \d tablename describes a table, \l lists databases, \du lists roles, and \timing shows how long each query takes.

Knowing these signals hands-on experience. \d+ gives extra detail like size and storage, and \e opens your editor for a longer query.

text
\l            -- list databases
\dt           -- list tables
\d employees  -- describe the employees table
\du           -- list roles
\timing on    -- show query timing

Q22. What is a transaction in PostgreSQL?

A transaction groups one or more statements so they succeed or fail together. You BEGIN, make changes, then COMMIT to save them or ROLLBACK to undo everything since BEGIN comes first.

Transactions give you the 'A' in ACID, atomicity: a transfer that debits one account and credits another either fully happens or fully doesn't, so you never leave money in limbo.

sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;   -- both happen, or ROLLBACK undoes both

Q23. What is the difference between COUNT(*), COUNT(column), and COUNT(DISTINCT column)?

COUNT(*) counts every row, including rows where columns are NULL. COUNT(column) counts only rows where that column is not NULL, so it can return a smaller number. COUNT(DISTINCT column) counts how many different non-NULL values that column has.

The NULL behavior is the point being tested. If you want a true row count use COUNT(*); if you want how many rows have a value in a column use COUNT(column).

sql
SELECT COUNT(*)              AS all_rows,
       COUNT(phone)          AS rows_with_phone,
       COUNT(DISTINCT team)  AS distinct_teams
FROM employees;

Key point: Interviewers ask this to check if you know COUNT(column) skips NULLs. Getting that distinction right is the whole point of the question.

Q24. What is the difference between SERIAL and GENERATED AS IDENTITY?

Both auto-generate increasing integer values for a column. SERIAL is the older shorthand that creates a sequence behind the scenes. GENERATED AS IDENTITY is the SQL-standard modern replacement and is the recommended choice today.

IDENTITY handles ownership and permissions of the underlying sequence more cleanly, and GENERATED ALWAYS AS IDENTITY blocks manual inserts into the column unless you override, which prevents accidental id collisions.

sql
-- preferred, modern form
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY

-- older shorthand (still works)
id BIGSERIAL PRIMARY KEY
Back to question list

PostgreSQL Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: query mechanics, indexing judgment, and the questions that separate SQL users from people who understand the engine.

Q25. What is MVCC and how does PostgreSQL implement it?

MVCC is Multi-Version Concurrency Control. Instead of locking rows for reads, PostgreSQL keeps multiple versions of each row, so every transaction sees a consistent snapshot of the data as of when it started. Readers don't block writers and writers don't block readers.

Under the hood, an UPDATE writes a new row version and marks the old one dead rather than overwriting in place. Each row carries hidden xmin and xmax columns that record which transactions created and expired it, and that's how visibility is decided.

sql
-- the hidden system columns that drive MVCC visibility
SELECT xmin, xmax, * FROM accounts WHERE id = 1;

Key point: The natural follow-up is 'so what cleans up the dead versions?'. the technical answer is VACUUM. Volunteering that shows you understand the whole picture.

Q26. What are VACUUM and autovacuum, and why do they matter?

Because MVCC leaves dead row versions behind after UPDATE and DELETE, tables would grow forever without cleanup. VACUUM reclaims that space for reuse, updates the visibility map, and refreshes planner statistics. Autovacuum runs it automatically in the background.

VACUUM (plain) makes space reusable but doesn't shrink the file; VACUUM FULL rewrites the table to return space to the OS but takes an exclusive lock. ANALYZE, which autovacuum also runs, keeps the statistics the planner relies on current.

CommandWhat it doesLock
VACUUMMarks dead tuple space reusableLight, allows reads and writes
VACUUM FULLRewrites table, returns space to OSExclusive, blocks the table
ANALYZEUpdates planner statisticsLight

Key point: 'What is table bloat?' is the companion question. Bloat is accumulated dead tuples; autovacuum falling behind on a write-heavy table is a classic production issue.

Q27. What index types does PostgreSQL offer, and when do you use each?

B-tree is the default and fits most equality and range queries on ordered data. GIN suits multi-value columns like JSONB, arrays, and full-text search. GiST handles geometric and nearest-neighbor searches. BRIN is tiny and works on huge, naturally ordered tables. Hash is for equality only.

The judgment being tested is matching the index to the query shape. Indexing a JSONB column with B-tree won't help containment queries; that's what GIN is for.

IndexBest forExample
B-treeEquality and range on ordered dataWHERE age > 30
GINArrays, JSONB, full-textWHERE tags @> '{sql}'
GiSTGeometric, nearest-neighborPostGIS distance queries
BRINHuge, naturally ordered tablesTime-series by timestamp

Watch a deeper explanation

Video: Database Indexing Explained (with PostgreSQL) (Hussein Nasser, YouTube)

Q28. How do you read EXPLAIN and EXPLAIN ANALYZE output?

EXPLAIN shows the plan the query planner intends to use, with estimated costs and row counts. EXPLAIN ANALYZE actually runs the query and reports real timings and real row counts, so you can compare the planner's estimate to reality.

Read it bottom-up: leaf nodes (scans) feed into joins and sorts. Watch for Seq Scan on large tables, a big gap between estimated and actual rows (stale statistics), and expensive sorts that spill to disk.

sql
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 42 ORDER BY created_at DESC;
-- look for: Index Scan vs Seq Scan, estimated vs actual rows, total time

Key point: Interviewers care that you measure rather than guess. Saying 'I'd run EXPLAIN ANALYZE and compare estimated to actual rows' is the process they're scoring.

Q29. What transaction isolation levels does PostgreSQL support?

PostgreSQL supports Read Committed (the default), Repeatable Read, and Serializable. Higher levels prevent more anomalies. Read Uncommitted is accepted as syntax but behaves like Read Committed, since Postgres never shows uncommitted data.

Read Committed sees each statement's fresh snapshot. Repeatable Read locks in one snapshot for the whole transaction, preventing non-repeatable and phantom reads. Serializable adds detection of write conflicts and may abort a transaction you must retry.

LevelPreventsCost
Read CommittedDirty readsDefault, cheapest
Repeatable ReadDirty, non-repeatable, phantom readsOne snapshot per transaction
SerializableAll of the above plus serialization anomaliesMay abort, needs retry logic

Key point: The follow-up is 'what do you do at Serializable when a transaction fails?'. Answer: catch the serialization_failure and retry. Missing retry logic is the trap.

Watch a deeper explanation

Video: ACID Properties in Databases With Examples (ByteByteGo, YouTube)

Q30. What are the ACID properties and how does PostgreSQL guarantee them?

Atomicity: a transaction's statements all commit or all roll back. Consistency: constraints keep the database valid. Isolation: concurrent transactions don't step on each other, controlled by isolation levels. Durability: committed data survives a crash.

PostgreSQL delivers atomicity and durability through the write-ahead log (WAL): changes are logged before they hit data files, so a crash can replay committed work. Isolation comes from MVCC and locks, consistency from constraints and triggers.

Q31. When would you use a subquery versus a JOIN?

Use a JOIN when you need columns from both tables in the result. Use a subquery when you only need one table's rows filtered by a condition that involves another table, like existence or membership checks.

For existence, prefer EXISTS over IN with a subquery: EXISTS stops at the first match and handles NULLs cleanly, while IN with a NULL in the list can silently return no rows.

sql
-- customers who have at least one order
SELECT name FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

Q32. What is a CTE (WITH clause) and when is it useful?

A common table expression is a named temporary result you define with WITH and reference in the main query. It makes complex queries readable by breaking them into named steps instead of nesting subqueries.

CTEs also enable recursion (WITH RECURSIVE) for hierarchical data like org charts or category trees. Note that since PostgreSQL 12, CTEs can be inlined by the planner, so they're no longer an optimization fence by default.

sql
WITH team_totals AS (
    SELECT team, SUM(salary) AS total
    FROM employees
    GROUP BY team
)
SELECT * FROM team_totals WHERE total > 500000;

Q33. What are window functions and how do they differ from GROUP BY?

Window functions compute a value across a set of rows related to the current row, without collapsing them. GROUP BY returns one row per group; a window function keeps every row and adds the computed column alongside.

They're the right tool for running totals, rankings, and 'compare each row to its group'. ROW_NUMBER, RANK, LAG, LEAD, and SUM(...) OVER (...) are the common ones. The OVER clause defines the window with PARTITION BY and ORDER BY.

sql
SELECT name, team, salary,
       RANK() OVER (PARTITION BY team ORDER BY salary DESC) AS rank_in_team
FROM employees;

Key point: 'How would you get the top 3 earners per team?' is a favorite. Window functions plus a filter on ROW_NUMBER or RANK is the clean answer.

Q34. How does PostgreSQL handle JSON, and what is the difference between JSON and JSONB?

PostgreSQL stores JSON in two types. JSON keeps the exact text including whitespace and key order and reparses on each access. JSONB stores a decomposed binary form that's faster to query, supports indexing with GIN, and removes duplicate keys.

For almost anything you query or index, use JSONB. Operators like -> (get field), ->> (get field as text), and @> (contains) work on it, and GIN indexes make containment queries fast.

sql
CREATE INDEX idx_props ON events USING GIN (props);

SELECT id
FROM events
WHERE props @> '{"type": "signup"}';   -- containment, uses the GIN index

Q35. What is normalization, and what are the first three normal forms?

Normalization organizes tables to reduce redundancy and avoid update anomalies. First normal form: atomic columns, no repeating groups. Second: no partial dependency on part of a composite key. Third: no non-key column depends on another non-key column.

The practical goal is one fact in one place. You normalize to keep writes consistent, then selectively denormalize for read performance when profiling proves it's needed, not before.

Q36. When would you denormalize, and what are the trade-offs?

Denormalize when a fully normalized schema forces expensive joins on a hot read path and you've measured that the joins are the bottleneck. Storing a computed total or a duplicated label trades write complexity for read speed.

The cost is consistency: duplicated data can drift, so you need triggers, application logic, or scheduled jobs to keep copies in sync. Normalize by default, denormalize deliberately with a reason you can defend.

Q37. What is the difference between a view and a materialized view?

A view is a saved query that runs fresh every time you select from it, so it always shows current data but costs the full query each read. A materialized view stores the query's results physically, so reads are fast but the data is a snapshot until you refresh it.

Use a plain view to simplify or secure access to complex queries. Use a materialized view for expensive aggregations that can tolerate slightly stale data, and refresh it on a schedule, ideally CONCURRENTLY so reads aren't blocked.

sql
CREATE MATERIALIZED VIEW daily_sales AS
SELECT date_trunc('day', created_at) AS day, SUM(total) AS revenue
FROM orders GROUP BY 1;

REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;

Q38. What is the difference between a function and a procedure in PostgreSQL?

A function returns a value and runs inside the calling transaction, so it can't COMMIT or ROLLBACK on its own. A procedure (added in PostgreSQL 11) is called with CALL, doesn't have to return anything, and can manage transactions inside its body.

Both can be written in PL/pgSQL, PostgreSQL's procedural language, or in SQL. Reach for a procedure when you need transaction control in the routine itself, like batch jobs that commit in chunks.

sql
CREATE FUNCTION team_size(t TEXT) RETURNS integer AS $$
  SELECT COUNT(*)::int FROM employees WHERE team = t;
$$ LANGUAGE sql;

SELECT team_size('platform');

Q39. What are triggers and when should you use them?

A trigger runs a function automatically in response to INSERT, UPDATE, DELETE, or TRUNCATE on a table, either before or after the event. Common uses are auditing changes, maintaining a denormalized value, or validating complex rules.

The counterpoint worth stating: triggers hide logic from application code, so overusing them makes behavior hard to trace. Keep them for data-integrity concerns that must hold no matter which client writes.

sql
CREATE FUNCTION set_updated_at() RETURNS trigger AS $$
BEGIN
  NEW.updated_at := now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_updated
BEFORE UPDATE ON employees
FOR EACH ROW EXECUTE FUNCTION set_updated_at();

Q40. What is a sequence and how do currval and nextval work?

A sequence is a database object that generates unique increasing numbers, which is what powers auto-incrementing ids. nextval advances it and returns the new value, currval returns the last value this session got, and setval resets it.

Sequences are not transactional for gap-freeness: a rolled-back transaction still consumes the number it drew, so ids can have gaps. That's expected; don't rely on sequences for consecutive, gap-free numbering.

sql
CREATE SEQUENCE order_no_seq;
SELECT nextval('order_no_seq');   -- 1
SELECT nextval('order_no_seq');   -- 2
SELECT currval('order_no_seq');   -- 2, this session

Q41. How does the CASE expression work?

CASE is SQL's conditional expression. It evaluates conditions in order and returns the first match's result, with an optional ELSE fallback. You use it to bucket values, pivot data, or apply conditional logic inside SELECT, ORDER BY, or aggregates.

A clean trick is conditional aggregation: SUM(CASE WHEN ... THEN 1 ELSE 0 END) counts rows meeting a condition per group, effectively pivoting rows into columns.

sql
SELECT team,
       COUNT(*) AS total,
       SUM(CASE WHEN active THEN 1 ELSE 0 END) AS active_count
FROM employees
GROUP BY team;

Q42. Why do you need connection pooling with PostgreSQL?

Every PostgreSQL connection is a separate OS process with real memory overhead, so opening a fresh connection per request is expensive and the server has a hard connection limit. A pooler keeps a set of connections open and hands them out, so apps reuse them.

PgBouncer is the common external pooler, running in transaction or session mode. Application frameworks also pool. Without pooling, a traffic spike exhausts connections and new requests fail with 'too many clients'.

Q43. Why use NUMERIC instead of FLOAT for money, and how do string functions help clean data?

NUMERIC stores exact decimal values, so 0.1 plus 0.2 is exactly 0.3, while FLOAT and DOUBLE PRECISION are binary floating point and introduce tiny rounding errors that corrupt financial totals. For money and any value where exactness matters, use NUMERIC with a defined precision and scale.

For cleaning text, PostgreSQL ships functions like TRIM, LOWER, UPPER, LEFT, SUBSTRING, REPLACE, and SPLIT_PART, plus regexp_replace for pattern-based edits. Combining them in a query normalizes messy input without touching the application.

sql
SELECT 0.1::numeric + 0.2::numeric AS exact,   -- 0.3
       0.1::float8 + 0.2::float8   AS floaty;  -- 0.30000000000000004

SELECT LOWER(TRIM(email)) AS clean_email FROM signups;

Key point: The money-precision answer is the one they want. If you volunteer NUMERIC(10,2) for currency you've signaled you've been burned by float rounding before.

Q44. How do you back up and restore a PostgreSQL database?

pg_dump produces a logical backup of one database as a script or archive, and pg_restore loads an archive-format dump. For a whole cluster, pg_dumpall includes roles and tablespaces. For large or always-on systems, physical base backups with WAL archiving support point-in-time recovery.

The distinction the key signal is: logical dumps are portable and good for smaller databases, while physical backups plus WAL are how you restore a big production system to a specific moment.

bash
pg_dump -Fc mydb > mydb.dump        # custom-format logical backup
pg_restore -d mydb_new mydb.dump    # restore into a new database
Back to question list

PostgreSQL Interview Questions for Experienced Engineers

Experienced16 questions

advanced rounds probe internals, locking, replication, and production scars. Expect every answer here to draw a follow-up.

Q45. What is the write-ahead log (WAL) and what does it do?

The WAL is an append-only log where PostgreSQL records every change before applying it to the data files. On commit, the relevant WAL is flushed to disk, which is what makes durability cheap: a crash replays committed WAL records to recover.

WAL is also the backbone of replication and point-in-time recovery. Streaming replication ships WAL to standbys, and archiving WAL lets you restore a base backup and replay forward to any moment.

Key point: Follow-ups probe checkpoints and fsync. Knowing that a checkpoint flushes dirty pages so WAL can be recycled shows you understand the durability machinery, not just the acronym.

Q46. What kinds of locks does PostgreSQL use, and what is SELECT FOR UPDATE?

PostgreSQL takes table-level and row-level locks automatically, and MVCC means plain reads take no row locks at all. When you need to lock rows you're about to modify, SELECT ... FOR UPDATE locks them so no other transaction can change them until you commit.

FOR UPDATE is the tool for read-modify-write patterns like reserving inventory. Add SKIP LOCKED to build a work queue where each worker grabs different rows, or NOWAIT to fail fast instead of waiting.

sql
BEGIN;
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 1;
-- process the job, then UPDATE its status and COMMIT

Q47. What causes a deadlock and how does PostgreSQL handle it?

A deadlock happens when two transactions each hold a lock the other needs, so neither can proceed. PostgreSQL detects the cycle and aborts one transaction with a deadlock error, freeing the other. Your code catches that error and retries.

The prevention that experienced engineers cite: acquire locks in a consistent order across your application, keep transactions short, and touch rows in the same sequence everywhere. Consistent lock ordering removes most deadlocks entirely.

Handling deadlocks in practice

1Detect
Postgres finds the lock cycle and aborts one transaction
2Catch
your app catches the deadlock error (SQLSTATE 40P01)
3Retry
re-run the transaction, usually with a short backoff
4Prevent
acquire locks in a consistent order and keep transactions short

Deadlocks are expected under contention. Not having retry logic is the real bug, not the deadlock itself.

Key point: 'How do you prevent deadlocks?' is the follow-up they wait for. Consistent lock ordering and short transactions is the answer that indicates production experience.

Q48. How does the PostgreSQL query planner decide on a plan?

The planner is cost-based. It estimates the cost of alternative plans using table statistics (row counts, value distributions, most-common values) that ANALYZE collects, then picks the cheapest. That's why it might choose a sequential scan over an index when it expects to return most of a table.

When plans go wrong, the usual cause is stale or missing statistics, so estimated rows diverge from actual. Running ANALYZE, raising the statistics target on skewed columns, or adding an index the planner will actually use are the fixes.

Key point: If asked 'why is Postgres ignoring my index?', the strong answer is: the planner estimates a seq scan is cheaper for that row count, often because statistics are stale or the query returns too many rows.

Q49. What is table partitioning and when should you use it?

Partitioning splits one logical table into smaller physical child tables by range, list, or hash on a partition key. Queries that filter on the key touch only the relevant partitions (partition pruning), and you can drop old data by dropping a whole partition instantly.

It earns its keep on very large tables, especially time-series data partitioned by date. The cost is management overhead and that the partition key should appear in most queries; partitioning a table that queries ignore the key on adds complexity for little gain.

sql
CREATE TABLE events (id bigint, created_at date, payload jsonb)
PARTITION BY RANGE (created_at);

CREATE TABLE events_2026_q3 PARTITION OF events
FOR VALUES FROM ('2026-07-01') TO ('2026-10-01');

Q50. How does replication work in PostgreSQL?

Physical streaming replication ships WAL records from a primary to one or more standbys that replay them, keeping byte-for-byte copies for read scaling and failover. It's asynchronous by default; synchronous mode makes the primary wait for a standby to confirm, trading latency for zero data loss on failover.

Logical replication (publications and subscriptions) replicates selected tables at the row level, which supports version upgrades, selective sync, and replicating into a differently-structured target. Physical is for whole-cluster high availability; logical is for flexible, table-level flows.

TypeGranularityBest for
Physical (streaming)Whole cluster, WAL-basedHA, read replicas, failover
Logical (pub/sub)Selected tables, row-basedUpgrades, selective sync, ETL
SynchronousEither, waits for standbyZero-data-loss requirements

Q51. A query is slow in production. Walk through how you diagnose it.

Find the offenders first with pg_stat_statements, which ranks queries by total and mean time. Take the worst one and run EXPLAIN ANALYZE (BUFFERS) to see the real plan: is it a Seq Scan that should use an index, a bad row estimate from stale statistics, a sort spilling to disk, or an N+1 pattern from the app?

Then fix at the right layer and confirm with another measurement: add or adjust an index, rewrite the query, run ANALYZE, or tune work_mem for that workload. The structure, observe, hypothesize, verify, fix, confirm, matters more than any single tool.

sql
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Key point: The methodology is; naming pg_stat_statements and EXPLAIN ANALYZE is the supporting evidence.

Q52. What is an index-only scan, and what is a covering index?

An index-only scan answers a query entirely from the index without visiting the table heap, which is much faster because it reads less. It's possible when every column the query needs is in the index and the visibility map says the pages are all-visible.

A covering index deliberately includes extra columns with INCLUDE so more queries qualify for index-only scans. You put filter and join columns in the key and the returned columns in INCLUDE.

sql
CREATE INDEX idx_orders_cust_cover
ON orders (customer_id) INCLUDE (total, created_at);
-- SELECT total, created_at WHERE customer_id = ? can be index-only

Q53. What are partial and expression indexes?

A partial index covers only rows matching a WHERE condition, so it's smaller and faster when queries always filter on that condition, like indexing only active rows. An expression index indexes the result of a function, so a query using the same expression can use it.

Both target the exact queries you run. Indexing LOWER(email) lets case-insensitive lookups use an index; a partial index on WHERE status = 'open' keeps a hot-path index tiny.

sql
-- expression index for case-insensitive search
CREATE INDEX idx_email_lower ON users (LOWER(email));

-- partial index for a hot subset
CREATE INDEX idx_open_tickets ON tickets (created_at) WHERE status = 'open';

Q54. Why and how do you build an index without blocking writes?

A plain CREATE INDEX takes a lock that blocks writes to the table while it builds, which is unacceptable on a busy production table. CREATE INDEX CONCURRENTLY builds it without that lock by scanning the table twice, at the cost of taking longer and not running inside a transaction block.

The gotcha to mention: if a concurrent build fails, it leaves an invalid index you must drop and recreate. Checking for and cleaning up invalid indexes is part of doing this safely.

sql
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
-- does not block writes; if it fails, DROP the leftover invalid index

Q55. What is transaction ID wraparound and why does it matter?

PostgreSQL numbers transactions with a 32-bit counter that eventually wraps around. MVCC visibility depends on comparing these ids, so without maintenance old rows could suddenly look like they're in the future and become invisible. To prevent that, VACUUM freezes old rows, stamping them as permanently visible.

If autovacuum falls far behind on a high-write database, Postgres warns and ultimately stops accepting writes to force a freeze. Monitoring age(datfrozenxid) and keeping autovacuum healthy is how you avoid the emergency.

Key point: This question separates people who've run Postgres at scale from those who've only queried it. Even naming 'freeze' and 'autovacuum keeping up' signals operational experience.

Q56. What is TOAST in PostgreSQL?

TOAST (The Oversized-Attribute Storage Technique) is how PostgreSQL stores values too big for a single 8 KB page. Large text, JSONB, or bytea values are compressed and, if still large, sliced into chunks stored in a separate TOAST table, transparently to your queries.

It matters for performance because reading a row doesn't fetch its TOASTed columns unless you select them, and the storage strategy (PLAIN, EXTENDED, EXTERNAL) affects compression. Wide JSONB columns you rarely read are cheap precisely because of TOAST.

Q57. What are HOT updates and how does fillfactor help?

A Heap-Only Tuple (HOT) update is an optimization where an UPDATE that doesn't change any indexed column can store the new row version on the same page and skip updating the indexes, which cuts write amplification and index bloat.

For a HOT update to happen there must be free space on the page, so lowering fillfactor (say to 90) leaves room on each page for in-place updates. On update-heavy tables that don't change indexed columns, this reduces bloat noticeably.

sql
ALTER TABLE sessions SET (fillfactor = 90);
-- leaves free space per page so non-indexed updates can be HOT

Q58. What are extensions, and which ones come up often?

Extensions add packaged functionality to PostgreSQL with CREATE EXTENSION. pg_stat_statements tracks query performance, PostGIS adds geospatial types and functions, pgcrypto adds cryptographic functions, pg_trgm speeds up fuzzy text matching, and uuid-ossp generates UUIDs.

The extension system is a big reason teams choose Postgres: you get specialized capabilities without leaving the database. Naming pg_stat_statements and PostGIS shows you've worked with real deployments.

sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pg_trgm;  -- trigram fuzzy search

Q59. Should you use UUID or BIGINT for primary keys?

BIGINT identity keys are small, sequential, and index-friendly, so they pack tightly in B-trees and keep inserts local. UUIDs are globally unique without coordination, which suits distributed systems and client-generated ids, but random v4 UUIDs scatter inserts across the index and bloat it.

The nuance worth stating: if you need UUIDs, time-ordered variants (UUIDv7) restore insert locality, and their larger size still costs more space and memory than BIGINT. Pick BIGINT unless you specifically need decentralized id generation.

Key typeSizeTrade-off
BIGINT identity8 bytesCompact, sequential, needs central generation
UUID v4 (random)16 bytesGlobal, no coordination, scatters index inserts
UUID v7 (time-ordered)16 bytesGlobal and insert-friendly, still larger than bigint

Q60. How would you design a highly available PostgreSQL setup?

one primary and one or more streaming standbys, then add automated failover so a standby is promoted when the primary dies comes first. Tools like Patroni or repmgr handle leader election and promotion, and a connection router or virtual IP points clients at the current primary.

Round it out with the operational pieces: synchronous replication if zero data loss is required, regular base backups plus WAL archiving for point-in-time recovery, connection pooling in front, and monitoring on replication lag and disk. High availability is the whole stack, not a single flag.

Key point: The technical decision depends on whether you cover failover, backups, pooling, and monitoring together, not whether you The specific tool.

Back to question list

PostgreSQL vs MySQL, SQLite, and MongoDB

PostgreSQL wins when you want strict correctness, advanced SQL, and one engine that handles relational data and JSON without bolting on a second database. It trades a little raw simplicity for depth: standards-compliant SQL, extensible types, mature MVCC, and features like window functions and CTEs that ship in the core. Where it can lose is footprint and setup ceremony versus SQLite for embedded use, or ecosystem familiarity versus MySQL on some managed platforms. Knowing these trade-offs out loud is itself an interview signal: it shows you pick a database on merits, not habit.

DatabaseModelBest atWatch out for
PostgreSQLObject-relationalComplex queries, JSONB, correctness, extensionsHeavier to tune than SQLite
MySQLRelationalSimple web apps, wide hosting supportLooser SQL, fewer advanced features
SQLiteEmbedded relationalLocal, single-file, mobile, testsNo real concurrency for many writers
MongoDBDocument (NoSQL)Flexible schemas, denormalized documentsNo joins across collections, eventual consistency by default

How to Prepare for a PostgreSQL Interview

Prepare in layers, and practice on a real database. Most PostgreSQL rounds move from concept questions to live query writing to a design or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet against a local Postgres; running EXPLAIN on your own queries teaches more than any article.
  • Practice thinking aloud on small schema and query problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical PostgreSQL interview flow

1Recruiter or phone screen
background, motivation, a few concept checks
2SQL and concepts
joins, indexes, MVCC, isolation, normalization
3Live query writing
write and explain queries, read an EXPLAIN plan
4Design or debugging
schema trade-offs, slow-query diagnosis, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: PostgreSQL Quiz

Ready to test your PostgreSQL knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which PostgreSQL topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a PostgreSQL interview?

They cover the question-answer portion well, but most PostgreSQL rounds also include live query writing: writing and explaining SQL under observation, and reading an EXPLAIN plan. Practice against a real database with a timer. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Which PostgreSQL version do these answers assume?

Modern PostgreSQL, roughly version 12 and up, which covers what almost every team runs today. Features like generated columns, improved partitioning, and JSONB behavior are stable across these versions. When a feature is version-specific, the answer says so. If you're unsure in an interview, name your version and move on.

How long does it take to prepare for a PostgreSQL interview?

If you use Postgres at work or in study, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write SQL daily against a real database; reading answers without running queries is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, MVCC, indexing, joins, isolation levels, normalization, and the phrasing takes care of itself.

Is there a way to test my PostgreSQL knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These PostgreSQL questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 7 Jun 2026Last updated: 9 Jul 2026
Share: