Fullstack CourseLearn by building
Back to week 4

Topic

Challenge: missing index / slow list

Definition

A missing index causes the query planner to perform a sequential scan over an entire table to satisfy a filter or sort, producing latency that grows roughly linearly with table size instead of staying flat.

In simpler words

Without the right index, a filtered list gets slower and slower as the table grows, even though the code never changed.

This reuses the performance judgment from earlier in this week: recognizing the missing-index smell on a slow, filtered, frequently used endpoint.

After this you can

  • Recognize a filter or sort column with no supporting index
  • Explain why it was fast in development with a small table hides this bug
  • Pick the correct migration-based fix over an application workaround

The classic slow-list smell

Definition

A list endpoint that filters or sorts by a column with no index will scale acceptably in development, where tables are small, and degrade sharply in production, where tables are large, because sequential scan cost grows with row count.

In simpler words

The exact same query gets dramatically slower purely because the table grew. The code did not change; the data did.

A ticket list filtered by status and ordered by a created timestamp, with no index on either column, forces Postgres to read and sort every row on every request.

This bug is easy to miss locally with a few dozen seed rows and only shows up as a production incident once the table holds hundreds of thousands of rows.

Keep in mind

  • Fast in development with tiny seed data proves nothing about production scale.
  • Index columns used in filters and sorts on frequently hit endpoints.
  • Ship index changes as reviewed migrations, like any other schema change.

Challenge lab

Bug report

GET /tickets?status=open gets slower every week as the tickets table grows; it was instant in development.

What is broken

The tickets table has no index on status, so every filtered list performs a full sequential scan that gets more expensive as rows accumulate.

Broken snippet

// TicketsService
async list(status: string) {
  return this.ticketsRepo.find({ where: { status } }); // no index on status
}
// tickets table: only a primary key index on id

Pass criteria

  • Filtering by status uses an index instead of a full table scan
  • The fix ships as a reviewed migration, not an application-code workaround
  • Existing data and behavior are unaffected; only the query plan and performance change

Checking your session…

Choose the correct fix

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥80% (enforced by the API) to save progress.

Checking your session…

15 questions · concept 5 · syntax 3 · practical 4 · logic 3

1. How should the missing-index challenge be fixed?
Concept
2. Why not cache the full table in memory?
Syntax
3. Why not just enlarge the pool?
Practical
4. Why not filter in the frontend after download-all?
Logic
5. What should the migration contain?
Concept
6. This list endpoint gets slower as the table grows. What is the correct fix?
Practicalintermediate
async list(status: string) {
  return this.ticketsRepo.find({ where: { status } });
}
// tickets has only a primary-key index on id
7. Should you edit an old applied migration to add the index?
Syntax
8. What tool helps confirm the fix?
Logic
9. Do indexes replace pagination?
Concept
10. Which fix papers over sequential scans?
Practical
11. The plan shows a Seq Scan on a large table. What fixes it?
Conceptadvanced
EXPLAIN ANALYZE
SELECT * FROM tickets WHERE status = 'open';
12. Which index best serves this query?
Syntaxadvanced
SELECT * FROM tickets WHERE status = 'open' ORDER BY created_at DESC LIMIT 20;
13. Where should this statement live?
Practicalintermediate
await q.query('CREATE INDEX idx_tickets_status ON tickets(status)');
14. Why is this a poor fix for a slow filtered list?
Logicadvanced
let allTickets = await repo.find(); // cached in a module variable
15. How do you confirm the index helped?
Conceptintermediate
-- after adding the index
EXPLAIN ANALYZE SELECT * FROM tickets WHERE status = 'open';

Checking your session…