Back to Stream Practice: Employee Analysis
Q13Question 13 of 43

Paginate employees using skip and limit to fetch a specific page

Key Concepts
  • skip(n) discards the first n elements; limit(m) keeps at most the next m elements
  • Pagination formula: skip((pageNumber - 1) * pageSize).limit(pageSize)
  • The stream must be sorted first for pagination to be meaningful and deterministic
  • skip is a stateful intermediate operation; limit is short-circuiting
  • For large datasets, push LIMIT/OFFSET to the database instead of streaming the whole collection
  • skip beyond the stream size returns an empty stream, not an error

Definition

Use skip((page-1) * pageSize) to jump past earlier pages and limit(pageSize) to cap the result, implementing offset-based pagination on an ordered stream.
  • skip(n) – a stateful intermediate operation that discards the first n elements and passes the rest downstream.
  • limit(m) – a short-circuiting intermediate operation that stops the stream after m elements.
  • Pagination formula – skip((pageNumber - 1) * pageSize).limit(pageSize) returns one page of results.
  • Ordering requirement – the stream must be sorted first; without a deterministic order, the same page request could return different results.
  • Beyond-end – if skip exceeds the stream size, the result is empty, not an error.

Rajesh at the National Bank of India wants to browse the top-earners report three names per page. Page 1 shows the top 3. For page 2, he skips the 3 he already saw (skip 3) and takes the next 3 (limit 3). Skip is 'ignore these, I already saw them'. Limit is 'now give me only this many'.

Understanding the Concepts

What does this question ask?

The interviewer wants you to implement offset-based pagination using stream operations. This tests skip, limit, sorting, and understanding of stateful vs short-circuiting operations.

How do you implement pagination with skip and limit?

JAVA
package com.bank.hr;

import java.util.*;
import java.util.stream.Collectors;

public class PaginationDemo {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        int pageSize = 3;
        int pageNumber = 2;

        List<Employee> page2 = employees.stream()
            .sorted(Comparator.comparing(Employee::getSalary).reversed())
            .skip((long) (pageNumber - 1) * pageSize)  // skip 3
            .limit(pageSize)                             // take 3
            .toList();

        System.out.println("Page " + pageNumber + " (size " + pageSize + "):");
        page2.forEach(e ->
            System.out.println("  " + e.getName()
                + " Rs." + e.getSalary()));
    }
}
TEXT
Page 2 (size 3):
  Amit Rs.1400000
  Vikram Rs.1300000
  Deepak Rs.1200000

Page 1 (indices 0-2) has the top 3 earners. Page 2 skips those 3 and takes the next 3 (indices 3-5).

How does the pagination formula work?

DIAGRAM
Page 1: skip(0), limit(3) → elements 0, 1, 2
Page 2: skip(3), limit(3) → elements 3, 4, 5
Page 3: skip(6), limit(3) → elements 6, 7, 8
Page 4: skip(9), limit(3) → element 9 (only 1 left)
Page 5: skip(12), limit(3) → empty (beyond end)

The formula is skip((page - 1) * size). Page 1 skips 0, page 2 skips size, page 3 skips 2 * size, and so on.

Why must the stream be sorted first?

Without sorting, the stream processes elements in iteration order (insertion order for ArrayList). Two calls to the same pipeline might return different results if the source list is modified between calls.

JAVA
// WRONG: unsorted pagination is non-deterministic
List<Employee> unreliable = employees.stream()
    .skip(3)
    .limit(3)
    .toList();  // order depends on list insertion order

// CORRECT: sort first for deterministic pages
List<Employee> reliable = employees.stream()
    .sorted(Comparator.comparing(Employee::getSalary).reversed())
    .skip(3)
    .limit(3)
    .toList();  // always the 4th-6th highest earners

How do you build a reusable pagination utility?

JAVA
package com.bank.service;

import java.util.*;
import java.util.stream.Collectors;

public class PaginationService {

    public static <T> List<T> getPage(
            List<T> items,
            Comparator<T> order,
            int pageNumber,
            int pageSize) {
        if (pageNumber < 1 || pageSize < 1) {
            throw new IllegalArgumentException(
                "Page number and size must be positive");
        }
        return items.stream()
            .sorted(order)
            .skip((long) (pageNumber - 1) * pageSize)
            .limit(pageSize)
            .toList();
    }

    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();
        Comparator<Employee> bySalaryDesc =
            Comparator.comparing(Employee::getSalary).reversed();

        for (int page = 1; page <= 4; page++) {
            List<Employee> result = getPage(employees, bySalaryDesc, page, 3);
            System.out.println("Page " + page + ": "
                + result.stream()
                    .map(e -> e.getName() + "(" + e.getSalary() + ")")
                    .collect(Collectors.joining(", ")));
        }
    }
}
TEXT
Page 1: Rajesh(1800000), Deepak(1500000), Amit(1400000)
Page 2: Vikram(1300000), Kavita(1200000), Suresh(1100000)
Page 3: Priya(1000000), Ananya(900000), Meera(800000)
Page 4: Neha(700000)

What are common mistakes?

Mistake 1: using skip(page * pageSize) instead of skip((page - 1) * pageSize).

JAVA
// WRONG: page 1 skips pageSize elements, missing the first page entirely
List<Employee> wrong = employees.stream()
    .sorted(comp)
    .skip((long) pageNumber * pageSize)  // page 1 skips 3!
    .limit(pageSize)
    .toList();

// CORRECT: page 1 skips 0 elements
List<Employee> correct = employees.stream()
    .sorted(comp)
    .skip((long) (pageNumber - 1) * pageSize)  // page 1 skips 0
    .limit(pageSize)
    .toList();

Mistake 2: not handling the last page which may have fewer items.

JAVA
// Last page might have fewer items than pageSize
// limit(3) on a stream with only 1 remaining element returns just that 1
// This is correct behavior - no error, no padding
List<Employee> lastPage = employees.stream()
    .sorted(comp).skip(9).limit(3).toList();
System.out.println(lastPage.size());  // 1, not 3

What should a senior engineer know?

  • Streaming the entire list and skipping is fine for small in-memory datasets, but for production databases, push LIMIT and OFFSET to the SQL query so the database handles pagination.
  • Keyset pagination (WHERE id > lastSeenId ORDER BY id LIMIT n) is more efficient than OFFSET for deep pages because OFFSET still scans and discards rows.
  • In parallel streams, skip has overhead because it must respect encounter order, so elements before the skip point are still processed.

What are common interview follow-up questions?

  • "How do you compute the total number of pages?" (int) Math.ceil((double) total / pageSize).
  • "What happens if skip exceeds the stream size?" Returns an empty stream.
  • "How do you add page metadata (total, hasNext)?" Compute total count separately, derive hasNext from pageNumber * pageSize < total.

Visual Overview

DIAGRAM
  Sorted stream (salary desc):
  [1800K, 1500K, 1400K, 1300K, 1200K, 1100K, 1000K, 900K, 800K, 700K]
       │
       │  Page 2: skip(3)
       ▼
  [1300K, 1200K, 1100K, 1000K, 900K, 800K, 700K]  (first 3 discarded)
       │
       │  limit(3)
       ▼
  [1300K, 1200K, 1100K]  ← Page 2 result

  Formula: skip((page - 1) * size).limit(size)
  Page 1: skip(0).limit(3)  → [0..2]
  Page 2: skip(3).limit(3)  → [3..5]
  Page 3: skip(6).limit(3)  → [6..8]
  Page 4: skip(9).limit(3)  → [9]  (partial)

Key Takeaway

Pagination with streams uses skip to jump past earlier pages and limit to cap the current page. The formula is skip((pageNumber - 1) * pageSize).limit(pageSize). Page 2 with size 3 skips the first 3 elements and takes the next 3.

The stream must be sorted first for pagination to be deterministic. Without sorting, two requests for the same page could return different results. The sort creates a stable ordering that skip and limit then slice.

For the last page, limit naturally handles fewer remaining elements: if only 1 element remains, limit(3) returns just that 1 without error. If skip exceeds the stream size, the result is an empty list.

For production systems, push LIMIT and OFFSET to the database instead of loading the entire collection into memory. Keyset pagination (WHERE id > lastSeen) is more efficient than OFFSET for deep pages.

Remember one sentence: skip((page-1)*size).limit(size) on a sorted stream implements offset pagination, but for large datasets push pagination to the database.

---

Share
Use ← → arrow keys to navigate