Skip to main content
One idea at a time

Bound the work behind a task list

A frontend list shows a small portion of the workspace's tasks. The backend should also read a bounded portion rather than fetching the full table and slicing it in JavaScript.

Taskboard's task-list endpoints accept bounded pagination input. The server validates the page size and caps it so a caller cannot request an arbitrary amount of data.

An index helps a particular query​

An index stores an additional structure PostgreSQL can use to locate rows. A useful task-list index starts with the columns used to restrict the query, such as the workspace and project.

The rest of the index can support the requested ordering. An index on an unrelated column does not make every task query faster.

Indexes consume storage and make writes do extra work. PostgreSQL updates them when indexed data changes. Add an index for a demonstrated access pattern rather than adding one to every field.

EXPLAIN describes the query plan. EXPLAIN ANALYZE executes the query and reports observed execution information. That distinction matters for statements that modify data. A local plan over a handful of seed rows does not predict the plan for millions of production rows.

Stable ordering needs a tie-breaker​

Several tasks can share the same creation timestamp. Ordering only by that timestamp can produce ambiguous page boundaries. Adding the unique task identifier establishes a deterministic order.

Offset pagination skips a number of rows. It is familiar, but large offsets can require the database to walk past many records. New inserts can also shift page positions between requests.

Cursor pagination uses the last observed sort values to request the next set. For descending creation time, an illustrative boundary is:

-- Illustrative cursor boundary within an authorized workspace.
WHERE workspace_id = $1
AND (created_at, id) < ($2, $3)
ORDER BY created_at DESC, id DESC
LIMIT $4;

Taskboard uses this cursor approach for task lists and activity feeds. Its cursor encodes creation time and identifier, and the response supplies nextCursor when another page exists. Comment reads instead return a bounded latest set. Pagination belongs to each endpoint's contract rather than being a promise that all lists behave identically.

A frequent failure is paginating comments without tenant scope because their task identifier seems unique. Query performance changes must preserve the same access restrictions.

Does LIMIT 20 mean PostgreSQL examines only 20 rows?

No. PostgreSQL may scan or sort many candidate rows before finding the requested 20. The plan and supporting indexes determine the work.

Bound response size, define stable ordering, and inspect the actual query plan before trusting a performance assumption.

Read api/src/tasks.ts Read api/src/schema.ts