Question presented to candidate: "You have a nicely normalized schema, but the order list page and the revenue dashboard are slow because every request joins and sums. What is denormalization, when would you do it, and how do you keep the copied data correct?"
What a strong answer should cover:
- Denormalization is deliberately storing redundant or precomputed data (a copied column, a cached total or counter, a summary table) to make reads cheaper, starting from a normalized design rather than skipping it
- When it pays off: read-heavy paths measured to be slow, expensive aggregates on every request, analytics (star schemas), and values that must be frozen at a point in time (price or address on the order)
- The cost: every write must update the copies, so there is a sync mechanism (same transaction, trigger, materialized view refresh, or async job) and a defined staleness
- The tools on PostgreSQL: triggers, materialized views and
REFRESH MATERIALIZED VIEW [CONCURRENTLY], generated columns; plus a reconciliation query that detects drift - Alternatives to try first: indexes, covering indexes, query rewrites, caching outside the database
Clarifying questions expected:
- What is the read/write ratio, and which query is actually slow (with a plan)?
- How fresh must the denormalized value be: exact at commit time, or minutes behind?
- Is the copy a cache of current data or a historical snapshot that must not change?
Code / implementation expected: Yes β a cached column kept in sync (transaction or trigger), a summary via materialized view, and a drift check.
Schema used by the examples: