Definition
- 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?
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()));
}
}
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?
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.
// 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?
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(", ")));
}
}
}
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).
// 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.
// 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
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.
---