When the UI Has Pagination but the System Doesn't

A pagination component in the UI does not mean the system is actually paginating. Real pagination requires a clear API contract and coordinated backend, frontend, and UI behavior.

Bassem Hazem··4 min read
Network telemetry, API waterfall, and high-performance server architecture visual

I encountered this firsthand in a production application I was auditing. The application had a Projects module, which was the central operational dashboard for the entire business.

Visually, the UI looked immaculate: it had numbered page buttons, a "Next" chevron, and the browser URL query parameter even updated to reflect page=2&limit=10 on every click.

However, when I inspected the Network tab, the reality was startling: the backend endpoint was ignoring the query parameters entirely and returning all records in a single monolithic JSON payload.

If the client is requesting page 2 with a limit of 10 items, why is the backend serializing and returning all 500 records across the network?

With 50 records in staging, no one noticed. But what happens when that table reaches 500? 5,000? 50,000? At that point, you have pagination in the UI, but you have zero pagination at the system architecture level.

The Illusion: A Pagination Bar That Doesn't Paginate

The UI Slicing Anti-Pattern: Fetching an array of 5,000 items and running items.slice((page - 1) * limit, page * limit) in a React or Vue component is not pagination. It is a full-table database dump masquerading behind client-side cosmetic filtering.

When this anti-pattern is deployed, the server undergoes immense memory pressure to serialize thousands of records, the network transmits megabytes of unnecessary JSON payload, and the client browser freezes while parsing and garbage collecting massive objects.

The Hidden Resource Tax of Unpaginated APIs

Returning unpaginated data creates a compound resource tax across every layer of the infrastructure stack:

  • Database I/O & Memory: The database must allocate memory to read and construct full collection result sets rather than using indexed cursors or limit clauses.

  • Server CPU Serialization: JSON stringifying thousands of deeply nested objects blocks the Node.js event loop.

  • Network Bandwidth: Payloads inflate from 2 KB to several megabytes, increasing latency and data consumption.

  • Mobile Battery & Memory: Mobile devices on cellular networks drain battery and can crash under high memory usage.

  • DOM Rendering Latency: Unnecessary re-renders cause frame drops and sluggish user experience.

The End-to-End API Contract

Pagination is fundamentally a shared architectural contract between the server, the client, and the database. It begins with a strict request and response DTO specification:

The Core Pagination DTO Standard: A robust pagination response must encapsulate both the sliced records and essential metadata:

interface PaginatedResult<T> {
  items: T[];
  pagination: {
    page: number;
    limit: number;
    totalItems: number;
    totalPages: number;
    hasNextPage: boolean;
    hasPreviousPage: boolean;
  };
}

With this contract, the frontend knows exactly which page it is rendering, whether to disable the "Next" button, and how many total pages to show—without ever downloading the rest of the dataset.

Designing the Rules: Fixed vs. Dynamic Limits

A reliable API contract must enforce operational boundaries:

  • Default Page Size: Pick a sensible default (e.g., limit=10 or 20) so requests without query params don't fail.

  • User Selection Options: Allow users to select from a predefined whitelist (e.g., 10, 25, 50, 100).

  • Hard Maximum Cap: Always enforce a strict server-side ceiling (e.g., Math.min(limit, 100)) to prevent malicious clients from sending limit=1000000.

Architectural Comparison: UI Slicing vs. Backend Pagination

Metric

Client-Side Array Slicing

Server-Side Contract Pagination

Network Payload

Massive (Transfers entire DB collection every request).

Minimal (Transfers strictly the requested page of data).

Database Execution

Scans and allocates memory for every document in the table.

Limits query execution at the database engine level (skip/limit or keyset).

Scalability Ceiling

Degrades rapidly beyond 500 records; crashes mobile devices.

Scales seamlessly to millions of rows with proper indexing.

Mobile Experience

High mobile data consumption and battery drain.

Fast, lightweight, sub-second responses on any network.

The UI/UX Designer's Role in List Architecture

Engineering and design must align before implementation begins. A designer needs to account for pagination states in their Figma designs:

  • What does the skeleton loading state look like while page 2 is fetching?

  • How does the component handle zero records (the empty state)?

  • Does the UI preserve the user's scroll position or smoothly reset to the top upon page navigation?

  • What happens when a search filter narrows down the results from 50 pages to 1?

Beyond Pagination: The Full Scalability Strategy

Pagination is the essential first step, but it must be paired with comprehensive backend performance design. For high-scale systems, consider:

  • Offset vs Keyset: For massive datasets, keyset (cursor-based) pagination outperforms offset-based skip() queries.

  • Covering Indexes: Ensure the fields used for sorting and filtering are indexed to avoid full table scans.

  • Response Caching: Cache frequent first-page queries using Redis or HTTP cache headers.

Key Takeaways: Designing for Tomorrow's Scale

If the frontend sends page=2&limit=10 but the backend returns all 500 records, you implemented pagination in the UI, not in the system. Design for the data you expect to handle tomorrow.

  • Pagination is a distributed contract: database query, API specification, and frontend UI state.

  • Never rely on client-side array slicing for production data.

  • Always enforce a hard server-side limit ceiling to guard against Denial of Service.

  • Align designers and developers early on loading states, empty views, and pagination controls.

Share:𝕏in