Back to Stream: filter
Q1Question 1 of 19

Intermediate operations: filter, map, sorted, distinct, limit, skip, peek, flatMap

Key Concepts
  • All intermediate operations are lazy: they return a new Stream and execute only when a terminal triggers the pipeline.
  • Stateless operations (filter, map, flatMap, peek) process each element independently with O(1) memory.
  • Stateful operations (sorted, distinct) must buffer elements: sorted needs O(n) memory for the entire stream.
  • filter(Predicate) keeps elements where the predicate returns true; map(Function) transforms each element one-to-one.
  • flatMap(Function) transforms one element into many and flattens: Stream of List of T becomes Stream of T.
  • limit(n) takes the first n elements (short-circuit); skip(n) drops the first n elements.
  • sorted() on an infinite stream hangs forever because it must see all elements before sorting.

Definition

Intermediate operations are lazy transformations returning a new Stream: filter keeps, map transforms, flatMap flattens, sorted buffers all, distinct uses HashSet, limit takes n, skip drops n, peek debugs.
  • filter(Predicate) keeps elements where the predicate returns true. Stateless, O(1).
  • map(Function) transforms each element one-to-one. Stateless, O(1).
  • flatMap(Function) transforms each element into a Stream and merges all streams into one. Stateless.
  • sorted() sorts by natural order or Comparator. Stateful: buffers entire stream, O(n).
  • distinct() removes duplicates using hashCode and equals. Stateful: maintains HashSet, O(n).
  • limit(n) takes first n elements. Short-circuit: stops after n.
  • skip(n) drops first n elements.
  • peek(Consumer) observes each element for debugging. Do not use for business logic.

Think of eight stations on the cheque-clearing line at National Bank of India. Filter is the verification clerk: valid cheques pass, invalid ones are rejected. Map is the exchange counter: dollar cheques become rupee cheques. FlatMap is the envelope opener: one envelope with multiple cheques becomes individual cheques on the belt. Sorted is the sorting tray: all cheques must arrive before they can be ordered. Distinct is the duplicate checker: only unique cheques pass.

Understanding the Concepts

What are intermediate operations?

All intermediate operations are lazy. They return a new Stream and execute only when a terminal operation triggers the pipeline.

How does filter work?

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.List;

public class FilterDemo {
    record Account(String id, BigDecimal balance, boolean active) {}

    public static void main(String[] args) {
        List<Account> accounts = List.of(
            new Account("SAV2001", new BigDecimal("125000"), true),
            new Account("SAV2002", new BigDecimal("8000"), false),
            new Account("CUR3001", new BigDecimal("250000"), true)
        );

        // filter: keep active accounts
        accounts.stream()
            .filter(Account::active)
            .forEach(a -> System.out.println(a.id() + ": Rs." + a.balance()));
    }
}
TEXT
SAV2001: Rs.125000
CUR3001: Rs.250000

How does map work?

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.List;

public class MapDemo {
    record Account(String id, BigDecimal balance) {}

    public static void main(String[] args) {
        List<Account> accounts = List.of(
            new Account("SAV2001", new BigDecimal("125000")),
            new Account("CUR3001", new BigDecimal("250000"))
        );

        // map: Account -> String (extract formatted balance)
        List<String> summaries = accounts.stream()
            .map(a -> a.id() + ": Rs." + a.balance())
            .toList();

        System.out.println(summaries);
    }
}
TEXT
[SAV2001: Rs.125000, CUR3001: Rs.250000]

How does flatMap work?

JAVA
package com.bank.service;

import java.util.List;

public class FlatMapDemo {
    record Customer(String name, List<String> accountIds) {}

    public static void main(String[] args) {
        List<Customer> customers = List.of(
            new Customer("Rajesh", List.of("SAV2001", "CUR3001")),
            new Customer("Priya", List.of("SAV2002")),
            new Customer("Amit", List.of("SAV2003", "FD4001", "CUR3002"))
        );

        // Without flatMap: Stream<List<String>> (nested!)
        // With flatMap: Stream<String> (flat!)
        List<String> allAccounts = customers.stream()
            .flatMap(c -> c.accountIds().stream())
            .toList();

        System.out.println(allAccounts);
    }
}
TEXT
[SAV2001, CUR3001, SAV2002, SAV2003, FD4001, CUR3002]

How are stateful operations different from stateless?

| Operation | Stateful? | Memory | Safe on infinite stream? |
|-----------|-----------|--------|-------------------------|
| filter | No | O(1) | Yes |
| map | No | O(1) | Yes |
| flatMap | No | O(1) per element | Yes |
| peek | No | O(1) | Yes |
| sorted | Yes | O(n) | No: hangs or OOM |
| distinct | Yes | O(n) | Technically yes, but grows forever |
| limit | Yes | O(1) counter | Yes: short-circuits |
| skip | Yes | O(1) counter | Yes |

What is the performance tip for operation ordering?

JAVA
package com.bank.core;

import java.util.List;
import java.util.Comparator;
import java.math.BigDecimal;

public class OrderingTip {
    record Account(String id, BigDecimal balance, boolean active) {}

    public static void main(String[] args) {
        List<Account> accounts = List.of(
            new Account("SAV2001", new BigDecimal("125000"), true),
            new Account("SAV2002", new BigDecimal("8000"), false),
            new Account("CUR3001", new BigDecimal("250000"), true),
            new Account("SAV2003", new BigDecimal("50000"), false),
            new Account("CUR3002", new BigDecimal("180000"), true)
        );

        // WRONG: sort ALL 5, THEN filter to 3
        // Sorts 5 elements unnecessarily

        // CORRECT: filter FIRST to 3, THEN sort 3
        List<Account> result = accounts.stream()
            .filter(Account::active)                                    // reduces to 3
            .sorted(Comparator.comparing(Account::balance).reversed())  // sorts only 3
            .toList();

        result.forEach(a -> System.out.println(a.id() + ": Rs." + a.balance()));
    }
}
TEXT
CUR3001: Rs.250000
CUR3002: Rs.180000
SAV2001: Rs.125000

What about peek for debugging?

peek accepts a Consumer and observes each element without modifying the stream. It is designed for debugging only. Do not use it for business logic because the JVM can optimise away peek calls and parallel streams execute peek in unpredictable order.

What should a senior engineer know?

The practical distinction between stateless and stateful matters for performance and correctness. Place filter before sorted to reduce the buffer size. Never use sorted on infinite streams. Use limit after filter when you need a bounded result from a potentially large source. Understanding these categories helps optimise pipeline ordering.

Visual Overview

DIAGRAM
  Intermediate Operations:
  ─────────────────────────
  STATELESS (O(1) memory):        STATEFUL (O(n) memory):
  ┌─────────────────────┐         ┌─────────────────────┐
  │ filter(Predicate)   │         │ sorted(Comparator)  │
  │ map(Function)       │         │ distinct()          │
  │ flatMap(Function)   │         │ limit(n) [counter]  │
  │ peek(Consumer)      │         │ skip(n)  [counter]  │
  └─────────────────────┘         └─────────────────────┘
  Process one element at a time    Must see multiple/all elements
  Safe on infinite streams         sorted: hangs on infinite

  Optimal ordering:
  filter → distinct → sorted → limit
  (reduce data early, sort less)

Key Takeaway

Intermediate operations are lazy transformations that build a pipeline description and return a new Stream. Stateless operations like filter, map, flatMap, and peek process each element independently with O(1) memory. Stateful operations like sorted and distinct buffer elements internally: sorted needs O(n) memory for the entire stream, and distinct maintains a HashSet.

The key operation ordering rule is to place filter and limit before sorted to minimise the buffer size. Filtering 100,000 accounts to 1,000 active ones before sorting means sorting 1,000 elements instead of 100,000: a significant performance difference.

flatMap is essential for nested structures: when each customer has a list of accounts, map produces Stream of List of String (nested), while flatMap produces Stream of String (flat), enabling individual account operations.

Remember one sentence: filter and map are stateless and safe on any stream, sorted and distinct buffer everything, so filter early and sort late.

---

Share
Use ← → arrow keys to navigate