skip to content

questions

5

What is a materialized view, and why might a system use one instead of running the same expensive query against source tables every time a client asks for the data?

level: juniorimportance: must knowfreq 70%

answer

  1. precompute vs recompute
  2. read store separate from source
  3. staleness = lag since last refresh
  4. REFRESH MATERIALIZED VIEW
  5. denormalize for reads

basics

~20 s

A materialized view is a saved copy of a query's result, stored ahead of time. Instead of recalculating joins and aggregates on every request, the app just reads the pre-built copy, which is much faster but can be slightly out of date.

solid answer

~40 s

A materialized view precomputes and persists the result of an expensive query — joins across several tables, aggregations, filters — as its own physical data store. Reads hit that stored projection directly instead of re-executing the source query, trading write-time or background-job cost for read-time speed. It's most valuable when the read pattern is frequent and the underlying query is costly (multi-table joins, aggregations over large datasets), and read latency matters more than having the absolute latest data. The cost is staleness: the view reflects data as of its last refresh, not the current source state, so you need a refresh strategy and a tolerance for lag.

go deeper

for a junior

Should recognize that a materialized view stores precomputed results rather than recomputing on every read, and give one plausible reason (speed) for why that helps. Doesn't need to know refresh mechanics in depth.

for a middle

Should distinguish a materialized view from an ordinary SQL view, name at least one refresh trigger (schedule, on-demand, event-driven), and identify staleness as the core trade-off.

for a senior

Should reason about production failure modes (stale-but-silent, refresh load on the source system, refresh races) and pick appropriate strategies for a given read/write ratio and freshness requirement.

for a principal

Should reason about this at the platform level: when materialized views justify a separate storage technology, how staleness SLAs get set and communicated across teams, and how to design refresh pipelines that don't become a reliability liability of their own.

## What a materialized view is A materialized view is a pattern where you take the result of a query — often one that joins several tables, aggregates rows, or filters a large dataset — and physically persist that result as its own stored data structure, rather than recomputing it from scratch on every read. This is distinct from an ordinary ("virtual") database view, which is just a saved query definition: every time you select from a virtual view, the database re-executes the underlying SQL against the live source tables. A materialized view instead writes the computed rows to disk (or to a separate read store) once, and subsequent reads simply fetch those precomputed rows. ## How the copy is built and refreshed **Mechanism, step by step:** - **Step 1.** You define a query over one or more source tables/services — for example, "total order value per customer per month" joining an orders table and an `order_items` table and grouping by customer and month. - **Step 2.** A refresh process runs that query and writes the output into a dedicated table, index, cache, or even a different storage engine optimized for reads (a search index, a columnar store, a key-value cache). - **Step 3.** Application read paths are pointed at the materialized copy instead of the source tables. - **Step 4.** On some cadence — on demand, on a schedule, or in response to change events from the source — the view is refreshed, meaning the stored result is recomputed (fully or incrementally) to catch up with changes made to the source data since the last refresh. ## Why it exists The pattern exists to solve the mismatch between how data is naturally structured for writes (normalized, transactional, optimized to avoid duplication and preserve integrity) and how it's consumed for reads (denormalized, precomputed, shaped exactly for a specific query or UI need). Recomputing a five-table join with aggregations on every page load doesn't scale — CPU cost, lock contention, and I/O all balloon under load, and the query planner has to redo work that produced an identical answer moments ago. By moving that computation out of the read's critical path — either into a background job or into the write path — you turn an O(expensive-join) read into an O(single-table-lookup) read. This is especially valuable for: - **reporting/analytics dashboards**; - **product catalogs** with computed inventory or pricing; - **search indexes**; - and any "read amplifies write" scenario where one write event has to be reflected across many downstream queries. ## What you gain, what you pay **Trade-offs, cost on both sides.** The benefit is read latency and read-side scalability — you can also scale the materialized store independently (different hardware, different indexing strategy, even a different database technology) from the source of truth. The cost is threefold. 1. **First, staleness:** the view is a snapshot as of its last refresh, so reads can return data that lags reality by anywhere from milliseconds (event-driven refresh) to hours (nightly batch refresh) depending on strategy. 2. **Second, storage and operational overhead:** you now maintain a second copy of derived data, with its own schema, its own refresh pipeline, its own monitoring, and its own failure modes — more moving parts than a single source of truth. 3. **Third, refresh cost:** recomputing the view, especially a full recompute, can itself be expensive and needs to be scheduled so it doesn't compete with production load; incremental refresh reduces this but adds implementation complexity. ## Failure modes in production - **The refresh job falls behind** (queue backpressure, a slow batch window, a crashed cron job) and the view silently serves increasingly stale data — dangerous when the staleness isn't visible to the consumer, e.g., a dashboard that looks fresh but is actually 6 hours old because last night's job failed. - **A "thundering herd" on full refresh** is another common failure: if the view is rebuilt by re-running the full query against source tables, that query itself can degrade the source system's performance, sometimes worse than the original problem the view was built to solve. - **A third failure mode is drift** between the view's schema assumptions and the source schema — a column rename or a new valid state value the transform logic doesn't account for, producing silently wrong aggregates rather than an error. - **Finally, refresh races:** if a refresh reads source rows mid-write without a snapshot/transaction boundary, the materialized result can be internally inconsistent. ## Concrete example An e-commerce platform's "top selling products this week" widget, shown on every homepage load, would be prohibitively expensive to compute live from the orders table for every visitor. Instead, a scheduled job (say, every 15 minutes) or a stream processor consuming order-completed events maintains a materialized `product_sales_rollup` table keyed by `product_id` with a running count and revenue sum; the homepage reads that single-row lookup instead of aggregating millions of order rows per request. PostgreSQL's native `CREATE MATERIALIZED VIEW ... WITH DATA` (refreshed via `REFRESH MATERIALIZED VIEW`) is a concrete, widely-known instance of this same underlying pattern.

  • How does a materialized view differ from a simple database cache (like a Redis key-value cache) of query results?
    A materialized view is typically a structured, queryable dataset with its own schema — you can filter, sort, and join against it like any table — whereas a simple cache usually stores an opaque blob keyed by a specific query signature. A materialized view is also usually maintained by a defined refresh pipeline tied to the source schema, while a cache is often invalidated more crudely (TTL expiry or explicit invalidation on write). In practice the line blurs — a Redis hash mirroring a computed rollup is functioning as a materialized view even if the tooling calls it a cache.
  • If a materialized view becomes stale because its refresh job crashes, how would you detect that in production before a user notices bad data?
    Track a 'last successful refresh' or 'last watermark' timestamp per materialized view and alert when it exceeds the expected refresh interval by some margin. Emit staleness as an explicit metric (e.g., 'view lag in seconds') rather than trusting the job's own success/failure logs, since a job can succeed while doing nothing useful. Some teams also surface the staleness timestamp to end users or downstream consumers so degraded freshness is visible rather than silent.
  • Why might recomputing a materialized view by fully re-running its defining query be worse for the source database than just querying it live occasionally?
    A full refresh re-executes the same expensive multi-table join/aggregation against the live source, but does so as one large batch operation instead of many small, naturally-spaced reads — this concentrates load, can hold locks longer, and competes with transactional write traffic on the same tables. If the refresh runs on a fixed schedule across many similar views, you also risk synchronized spikes ('thundering herd') hitting the source at the same moment.

Like a restaurant prepping mise en place before dinner service — chopping vegetables and pre-making sauces ahead of time so each order can be plated fast, instead of starting from raw ingredients for every single dish.

saying these in an interview costs you the question

  • Says materialized views are always eventually-consistent 'so it doesn't matter how stale they are'
  • Can't articulate any refresh strategy beyond 're-run the query on a timer'
  • Assumes a materialized view is interchangeable with an ordinary (virtual) SQL view with no persistence difference
  • Doesn't mention that maintaining the view has an ongoing storage/compute cost
  • No answer for what happens if the refresh job fails or falls behind

context

open as a page

When maintaining a materialized view, what are the trade-offs between refreshing it on-demand (when queried), on a fixed schedule, and in response to change events from the source data?

level: middleimportance: must knowfreq 75%

basics

~20 s

On-demand refresh recalculates the view only when someone asks, so it's always fresh but slow the first time. Scheduled refresh updates it every so often (like every hour), which is simple but can be stale between runs. Event-driven refresh updates it right when the source data changes, keeping it fresh with less wasted work, but is more complex to build.

open as a page

How would you decide, for a given feature, whether to serve reads from a materialized view (accepting some staleness) versus computing the result with a query-time join against the live source tables — and what breaks if you get that call wrong in either direction?

level: seniorimportance: must knowfreq 65%

basics

~20 s

Use a materialized view when the query is expensive and slightly old data is fine, like a dashboard. Use a live join when the data must always be current, like a bank balance. Picking wrong means either a slow, overloaded system or a fast one that shows outdated info to people who needed the latest.

open as a page

You're designing incremental refresh for a materialized view that maintains a running 'total spend per customer' from an orders table, instead of recomputing the full aggregate from scratch on every refresh. What does the incremental update logic need to handle correctly, and what can go wrong if it doesn't?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Instead of re-adding up every order every time, you just add or subtract the new order's amount to the existing total. But you also need to handle edited or canceled orders (subtract the old amount first) and make sure you don't double-count an order you've already processed.

open as a page

At what point does introducing a materialized view stop being worth it for a given read path, and what would you reach for instead?

level: principalimportance: should knowfreq 40%

basics

~20 s

If the data changes about as often as it's read, if slightly old data would cause real harm, or if a simple index or better query would already be fast enough, adding a materialized view just adds extra moving parts without enough benefit. Better indexing, caching, or just optimizing the query is often the simpler fix.

open as a page