Definition
- count() increments a counter for each matching element: no list allocation.
- collect(toList()).size() builds a full
ArrayList, then reads its size and discards it. - Wasted allocation: the list is created and immediately thrown away.
- Intent:
count()says 'I want a number';collect(toList())says 'I want the elements'. - Java 9+ optimization:
count()can skip the pipeline for SIZED streams without size-changing ops.
To know how many transaction forms pass a check, Priya at National Bank of India just tallies them as they go by, rather than photocopying every passing form into a pile and then counting the pile.
Understanding the Concepts
Why is count() more efficient?
filter(...).count() runs the filter and increments a counter for each surviving element. It never stores the elements, so there is no list allocation, no backing array resizing, and no garbage collection of the discarded list.
filter(...).collect(toList()).size() builds a complete ArrayList of all survivors, which allocates backing storage and grows it as needed, then simply reads the size and throws the list away.
How do you demonstrate the difference?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
public class CountVsCollectSize {
record Employee(String id, String name, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", new BigDecimal("1800000")),
new Employee("SAV2002", "Priya", new BigDecimal("1200000")),
new Employee("SAV2003", "Meera", new BigDecimal("1500000")),
new Employee("SAV2004", "Deepak", new BigDecimal("900000"))
);
BigDecimal threshold = new BigDecimal("1000000");
// EFFICIENT: count() – no list built
long count = staff.stream()
.filter(e -> e.salary().compareTo(threshold) > 0)
.count();
// WASTEFUL: builds a list just to read its size
int size = staff.stream()
.filter(e -> e.salary().compareTo(threshold) > 0)
.collect(Collectors.toList())
.size();
System.out.println("Count: " + count); // 3
System.out.println("Size: " + size); // 3
}
}
What about the Java 9+ count optimization?
In Java 9+, if the stream is SIZED and no size-changing operation (like filter) is present, count() can return the known size without traversing at all.
// No filter: count() returns known size directly (Java 9+)
long count = staff.stream().count(); // returns 4 without traversal
// With filter: count() must still evaluate the predicate
long filtered = staff.stream()
.filter(e -> e.salary().compareTo(threshold) > 0)
.count(); // evaluates filter per element, but no list built
When should you use collect instead of count?
Use collect when you actually need the elements, not just the quantity.
| Need | Terminal operation |
|---|---|
| Just the count | count() |
| The elements themselves | collect(toList()) |
| Both count and elements | collect(toList()) then list.size() |
What are common mistakes?
Mistake: Building a collection just to read its size.
// WRONG: wasteful list creation
int n = staff.stream().filter(predicate).collect(Collectors.toList()).size();
// CORRECT: count directly
long n = staff.stream().filter(predicate).count();
What should a senior engineer know?
Match the terminal operation to the intent. When you only need a quantity, use count(). When you need the elements, use collect. Do not build a collection just to read its size. The Java 9+ optimization is a bonus, but the real saving is avoiding needless list materialization.
What are common interview follow-up questions?
- "When does count skip traversal?" When the stream is SIZED and no size-changing operation is present (Java 9+).
- "Is count always faster than collect().size()?" In terms of allocation, yes. count never builds a list.
- "What if you need both the count and the list?" Collect once and call
list.size().
Visual Overview
count(): collect(toList()).size():
┌────────────────────────┐ ┌────────────────────────────┐
│ filter → counter++ │ │ filter → ArrayList.add() │
│ No storage │ │ Allocate, resize, fill │
│ Return: 3 │ │ Return: list.size() = 3 │
│ Memory: O(1) │ │ Memory: O(k) for k matches │
└────────────────────────┘ │ List discarded immediately │
└────────────────────────────┘
Key Takeaway
count() is more efficient than collect(toList()).size() because it only increments a counter as elements pass the filter, never storing them. The collect version builds a complete ArrayList, allocating and growing backing storage, then reads the size and throws the list away. For many matches, that is wasted time and memory.
At the National Bank of India, a transaction monitor that only reports how many high-value transactions occurred uses count() for exactly this reason. The principle is to match the terminal operation to the intent: count for quantity, collect for elements.
In Java 9+, count() can sometimes skip the pipeline entirely for SIZED streams without size-changing operations, but the reliable saving is simply avoiding list materialization.
Remember one sentence: do not build a collection just to read its size; use count() when you only need the quantity.
---