Explain the difference between map and filter as stream intermediate operations, including how they affect element count and type.
answer
- filter = 'should it stay?' (Predicate -> boolean)
- map = 'what does it become?' (Function -> new value)
- filter: count shrinks, type same
- map: count same, type can change
- Filter before map: fewer maps, avoid NPEs
basics
~20 sfilter keeps only elements that match a condition, so it can shrink the stream but never changes element type. map transforms each element into a new value (possibly a different type) one-for-one, so it keeps the same number of elements but can change what they are.
solid answer
~40 sfilter takes a Predicate<T> (a function returning boolean) and passes through only the elements for which it returns true; the element type is unchanged and the count can only stay the same or decrease. map takes a Function<T, R> and replaces each element with the function's result; it is one-to-one, so the count is always preserved, but the element type can change from T to R (e.g. String to Integer). A useful rule: filter answers 'should this element stay?', map answers 'what should this element become?'. They are commonly combined, and order matters for cost and correctness — filtering before mapping means you map fewer elements, and sometimes you must filter first to avoid NullPointerExceptions in the mapper. Neither mutates the source. For object-to-primitive transforms there are mapToInt/mapToLong/mapToDouble variants that return primitive streams.
code
java · 8 linesList<String> names = Arrays.asList("alice", null, "bob");
List<Integer> lengths = names.stream()
.filter(Objects::nonNull) // selection: drops null -> count shrinks
.map(String::length) // transform: String -> Integer (type changes)
.collect(Collectors.toList()); // [5, 3]
// map is one-to-one: input size 2 (after filter) -> output size 2go deeper
Can state that filter keeps matching elements and map transforms each element, and give a small correct example of each.
Explains the count/type contract precisely (filter: same-or-fewer, same type; map: same count, possibly new type) and knows filter takes a Predicate, map a Function.
Discusses ordering for cost and null-safety, the distinction from flatMap, and the primitive mapToInt/mapToLong/mapToDouble variants for performance.
Weighs readability and micro-performance trade-offs across a large codebase, and recognizes when a single map with a richer mapper, or a plain loop, communicates intent better than a long filter/map chain.
## The two questions `map` and `filter` are the two most common intermediate operations, and the clearest way to keep them straight is the question each one answers: - **`filter` asks: "Should this element stay in the stream?"** It is a *selection* / *gatekeeping* operation. - **`map` asks: "What should this element become?"** It is a *transformation* operation. ## filter in detail `Stream<T> filter(Predicate<? super T> predicate)` A **Predicate** is a function that takes one argument and returns a `boolean` — true or false. `filter` runs the predicate on every element. If it returns `true`, the element passes through; if `false`, the element is dropped. ```java List.of(1, 2, 3, 4, 5).stream() .filter(n -> n % 2 == 0) // keep evens .collect(Collectors.toList()); // [2, 4] ``` Key properties: - **Element type is unchanged.** A `Stream<Integer>` stays a `Stream<Integer>`. - **Count can only shrink or stay equal.** Filtering never adds or transforms elements. ## map in detail `<R> Stream<R> map(Function<? super T, ? extends R> mapper)` A **Function<T, R>** takes one argument of type T and returns a value of type R. `map` replaces every element with the result of applying the function. ```java List.of("a", "bb", "ccc").stream() .map(String::length) // String -> Integer .collect(Collectors.toList()); // [1, 2, 3] ``` Key properties: - **One-to-one.** Every input element produces exactly one output element — the count is **always preserved**. - **Type can change.** `Stream<String>` can become `Stream<Integer>`. This is why `map` is generic in a new type parameter `R`. ## Why count behaves differently | Operation | Can change element type? | Effect on count | |-----------|--------------------------|-----------------| | `filter` | No | Same or fewer | | `map` | Yes | Exactly the same| If you need *one element to become many* (e.g. flatten a list of lists), that is **`flatMap`**, a separate operation — `map` alone is strictly one-to-one. ## Order matters Because the pipeline is a single pass, the order of `filter` and `map` changes both cost and correctness: ```java // Filter first: map runs on fewer elements (cheaper if map is expensive) stream.filter(this::isValid).map(this::expensiveTransform); // Filtering first can also avoid errors: names.stream() .filter(Objects::nonNull) // drop nulls FIRST .map(String::toUpperCase) // safe: no NPE .collect(toList()); ``` If you mapped first and the mapper dereferenced a null, you would get a `NullPointerException`. ## Primitive variants When mapping objects to primitives, prefer `mapToInt`, `mapToLong`, `mapToDouble`. They return primitive streams (`IntStream`, etc.) that avoid boxing and unlock numeric terminal ops like `sum()` and `average()`: ```java int total = words.stream().mapToInt(String::length).sum(); ``` ## Neither mutates the source Both produce new pipeline stages; the original collection is never modified.
- If you need one element to expand into several elements, which operation do you use?flatMap. map is strictly one-to-one, so it cannot increase the element count. flatMap maps each element to a stream and then flattens all those streams into one, which is how you expand or flatten nested structures.
- Why might you put filter before map in a pipeline?Two reasons: performance — mapping runs on fewer elements if you discard unwanted ones first, which matters when the mapper is expensive; and correctness — filtering out nulls or invalid values first prevents the mapper from throwing (e.g. a NullPointerException).
Think of an assembly line of parcels. filter is a security gate that lets some parcels through and rejects others (same parcels, fewer of them). map is a relabeling station that swaps each parcel's contents for something new (same number of parcels, different contents).
saying these in an interview costs you the question
- Saying map can drop elements — it is one-to-one and always preserves count
- Saying filter can change element type — it never does
- Confusing map (one-to-one) with flatMap (one-to-many)
- Believing the order of filter and map never matters