Streams API
Stream pipelines end to end: sources, intermediate and terminal operations, the lazy fused execution model, collectors, primitive streams and parallelism. Expect at least one live coding question that turns a loop into a pipeline, plus follow-ups on why the pipeline behaves as it does.
part ofJavaoverview, primer and where to startread it →on this pageshowhide
explore
- Stream Creation Sources5 questions
- Stream Pipeline Anatomy5 questions
- Stream Laziness & Execution5 questions
- Stateless vs Stateful Operations5 questions
- Short-Circuiting Operations5 questions
- Encounter Order5 questions
- Intermediate Operations6 questions
- map vs flatMap4 questions
- Terminal Operations6 questions
- reduce & Reduction5 questions
- collect & Mutable Reduction5 questions
- Collectors: to Collection/Map5 questions
- Collectors: grouping & partitioning5 questions
- Collectors: downstream & adapters6 questions
- Primitive Streams6 questions
- Parallel Streams5 questions
- Stream Pitfalls6 questions
questions
89 · 17 sectionsWhat are the common ways to create a Stream in Java from existing data such as a collection, an array, or a few known values?
basics
~10 sCall collection.stream() on a List, Set, or Map's entries; use Arrays.stream(array) for arrays; Stream.of(a, b, c) for a handful of values; and Stream.empty() for an empty stream.
How do Stream.iterate and Stream.generate create infinite streams, and how do you make such a pipeline terminate?
basics
~10 sStream.iterate(seed, x -> next) and Stream.generate(supplier) produce endless streams. You must add a short-circuiting operation like limit(n) (or takeWhile/findFirst) so the pipeline stops; otherwise it runs forever.
How do you generate a stream of consecutive integers in Java, and what is the difference between IntStream.range and IntStream.rangeClosed?
basics
~10 sUse IntStream.range(start, end) for start up to end-1 (end excluded), or IntStream.rangeClosed(start, end) to include end. Both return an IntStream of consecutive ints.
How do you create a Stream over the lines of a file with Files.lines, and what resource-management concern does it introduce?
basics
~10 sFiles.lines(path) returns a Stream<String> of the file's lines, read lazily. It holds an open file handle, so you must close it — wrap it in try-with-resources, since Stream is AutoCloseable.
What is a Spliterator, and what role does it play as the underlying source of every Java stream?
basics
~20 sA Spliterator is the object that actually feeds a stream: it knows how to traverse elements one by one (tryAdvance) and how to split itself into chunks (trySplit) so a parallel stream can process pieces on different threads.
What are the three structural parts of a Java Stream pipeline, and what is the role of each?
basics
~20 sA stream pipeline has a source (where data comes from, like a list), zero or more intermediate operations (like filter or map that transform the stream), and exactly one terminal operation (like collect or forEach) that produces a result and runs the pipeline.
How does a Stream differ from a Collection in Java?
basics
~20 sA Collection stores data in memory and you can access it many times. A Stream stores nothing — it just describes a computation over a source and can only be used once, after which it is finished.
What is the single-use constraint of a Java Stream, and what happens if you violate it?
basics
~20 sA stream can only be operated on once. After you call a terminal operation (or even a second intermediate op), the stream is consumed. Trying to reuse it throws IllegalStateException with a message like 'stream has already been operated upon or closed'.
Explain how stream laziness enables operation fusion and short-circuiting, and what defeats these optimizations.
basics
~20 sBecause intermediate operations don't run until the terminal operation, the stream can push each element through the whole chain in one pass (fusion) and stop early once it has enough (short-circuiting, e.g. with findFirst or limit). Stateful operations like sorted or distinct that must see many elements at once break the single-pass flow.
What is a Spliterator and what role does it play as the source abstraction behind a stream pipeline?
basics
~20 sA Spliterator is the object that feeds elements from a source into a stream, one at a time. It is like an iterator that can also split itself in two, which is how streams divide work for parallel processing.
In Java's Streams API, what is the difference between intermediate and terminal operations, and why does nothing happen until a terminal operation is called?
basics
~20 sIntermediate operations like map and filter are lazy: they only set up a plan and return a new stream. Nothing actually runs until you call a terminal operation like collect, forEach, or count, which triggers the work.
How does a stream pipeline like source.filter(...).map(...).forEach(...) actually traverse elements — does it build an intermediate filtered list, and in what order are the operations applied?
basics
~20 sNo intermediate lists are built. Each element flows through the whole chain one at a time: an element is filtered, then if it passes, mapped, then handled by forEach, before the next element starts. The operations are fused into a single pass.
Why does peek sometimes print fewer elements than the source contains, for example in stream.peek(...).limit(2).collect(...)? Explain in terms of the execution model.
basics
~20 sBecause peek only runs for elements that are actually pulled through the pipeline. A short-circuiting op like limit(2) stops after two elements are produced, so the engine never asks the source for more, and peek never sees the rest.
Explain the difference between stateless and stateful intermediate stream operations, and why stateful ones (sorted, distinct, limit) interfere with the otherwise lazy, single-pass execution.
basics
~20 sStateless ops like map and filter handle each element on its own, so they pipeline cleanly. Stateful ops like sorted, distinct, limit, and skip need to remember earlier elements (or count them), so they may buffer or block the single-pass flow.
From a design standpoint, why did Java make stream intermediate operations lazy instead of eager like collection transformations, and what classes of optimization does that unlock?
basics
~20 sLaziness lets the whole pipeline run in one pass without building throwaway intermediate collections, and lets the engine stop early (short-circuit) and reorder/parallelize work. An eager design would materialize a new collection after every step and could never short-circuit.
In the Java Streams API, what is the difference between a stateless and a stateful intermediate operation? Give examples of each.
basics
~10 sA stateless operation processes each element on its own, knowing nothing about other elements (map, filter, flatMap, peek). A stateful operation needs to see other elements to produce its result (distinct, sorted, limit, skip).
How do stateful operations like sorted and distinct affect a stream pipeline's lazy single-pass fusion and memory behavior?
basics
~20 sNormally elements flow through the whole pipeline one at a time. A stateful op like sorted has to gather elements first, so it buffers them and stops that one-at-a-time flow until it has what it needs, using more memory.
You build an infinite stream with Stream.iterate and add sorted(). What happens, and which intermediate operations are safe on an infinite stream?
basics
~20 ssorted hangs forever (or runs out of memory) because it must see every element first, and an infinite stream never ends. Stateless ops like map and filter, plus the short-circuiting limit, are safe on infinite streams.
Why do stateful stream operations hurt parallel-stream performance more than stateless ones?
basics
~10 sParallel streams split data across threads and process chunks independently. Stateless ops parallelize cleanly. Stateful ops like sorted or distinct must combine results across threads, which adds synchronization and buffering, so they scale worse.
As a tech lead, what guidance would you give about using stateful stream operations in performance-sensitive or parallel code paths?
basics
~20 sPrefer stateless ops in hot paths; keep filters before stateful ops like sorted to shrink what they buffer; benchmark before using parallel streams with sorted/distinct, since their cross-thread coordination can make parallel slower than sequential.
What does it mean for a Stream operation to be short-circuiting, and why does it matter?
basics
~20 sA short-circuiting operation can stop processing elements as soon as the result is decided, instead of looking at every element. For example, anyMatch stops at the first matching element. This saves work and lets pipelines over very large or infinite streams finish.
How do anyMatch, allMatch, and noneMatch short-circuit, and what do they return on an empty stream?
basics
~20 sAll three are short-circuiting terminals returning a boolean. anyMatch stops and returns true at the first match; allMatch stops and returns false at the first element that fails; noneMatch stops and returns false at the first match. On an empty stream: anyMatch is false, while allMatch and noneMatch are both true (vacuously).
What is the difference between findFirst and findAny, and why do both return Optional?
basics
~20 sBoth are short-circuiting terminals that return one element wrapped in an Optional (empty if the stream had none). findFirst always returns the first element in encounter order. findAny may return any element — which lets it run faster in parallel because it doesn't have to respect order.
How does takeWhile differ from filter, and how does it relate to limit?
basics
~20 sfilter keeps every element that matches the condition, scanning the whole stream. takeWhile keeps elements from the start only while the condition holds, and stops at the first element that fails — so it short-circuits. limit is similar but cuts off by count instead of by a condition.
How does short-circuiting interact with parallel streams and encounter order, and what are the pitfalls?
basics
~20 sShort-circuiting still works in parallel — once one thread finds the deciding element, the others can stop. But operations that must respect encounter order (findFirst, limit, takeWhile) need extra coordination across threads, which can cancel out the parallel speed-up. Order-independent ones (anyMatch, findAny) parallelize cleanly.
What is encounter order in the Java Streams API, and what determines whether a stream has one?
basics
~20 sEncounter order is the order a stream's elements are processed in. A stream has it when its source is ordered, like a List or an array. Sources like HashSet have no defined order, so their streams are unordered.
What is the difference between findFirst() and findAny() on a stream, and when would you prefer each?
basics
~10 sfindFirst() returns the first element in encounter order. findAny() returns any matching element, with no order promise. Use findFirst when order matters; prefer findAny in parallel streams because it can be faster.
What is the difference between forEach() and forEachOrdered() on a stream, especially when the stream is parallel?
basics
~20 sforEach() runs an action on each element with no order guarantee — in a parallel stream elements may be handled in any order. forEachOrdered() always processes elements in encounter order, even in parallel, at the cost of speed.
What does the unordered() intermediate operation do, and how can it improve parallel stream performance?
basics
~20 sunordered() is a hint that tells the stream it no longer needs to preserve encounter order. It does not reorder anything itself, but it frees parallel operations like distinct() and limit() to skip the bookkeeping that maintaining order requires, which can be faster.
When designing a parallel stream pipeline, how do you reason about encounter order to keep results both correct and performant?
basics
~20 sDecide whether your result depends on order. If it does, keep an ordered source and use order-respecting operations (findFirst, forEachOrdered, collect to List). If order is irrelevant, use unordered sources or unordered()/findAny to let parallelism run faster. Never rely on accidental ordering.
Explain the difference between map and filter as stream intermediate operations, including how they affect element count and type.
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.
What are intermediate operations in the Java Streams API, and what does it mean that they are lazy?
basics
~20 sIntermediate operations like map, filter, and sorted transform a stream and return a new stream, so you can chain them. They are lazy: nothing actually runs until you add a terminal operation (such as collect or forEach) at the end.
What is the difference between stateful and stateless intermediate operations in the Streams API? Give examples and explain why it matters.
basics
~20 sA stateless operation (like map, filter, peek) handles each element on its own without remembering others. A stateful operation (like distinct, sorted, limit, skip) needs to remember or see other elements to do its job, so it may have to buffer data. Stateful ops cost more and can hurt parallel performance.
When and how should you use peek() and sorted() (including a custom comparator)? What are the pitfalls of each?
basics
~20 speek lets you look at each element as it flows by, mainly for debugging or logging, without changing it. sorted orders the stream, either by natural order or by a Comparator you pass in. Pitfalls: peek may not run for every element (and shouldn't change state), and sorted must buffer the whole stream and needs Comparable elements if you give no comparator.
Compare limit/skip with takeWhile/dropWhile (Java 9). How do they behave on ordered vs unordered and infinite streams?
basics
~20 slimit(n) keeps the first n elements and skip(n) drops the first n, both by position. takeWhile keeps elements from the start as long as a condition is true and stops at the first failure; dropWhile drops the leading run that matches and keeps the rest. limit and takeWhile can stop a stream early, so they work on infinite streams; skip and dropWhile generally must process the leading part.
What is the difference between Stream.map and Stream.flatMap in Java?
basics
~20 smap transforms each element into exactly one new element (one-to-one). flatMap turns each element into a stream of elements and merges them all into one flat stream (one-to-many), so you end up with more or fewer elements, not the same count.
How would you use flatMap to flatten a nested collection and split text into words, and why can't map do it?
basics
~20 sUse flatMap with a function that turns each element into a stream — e.g. List::stream to flatten a List<List<X>>, or splitting each line into a stream of words. map can't because it would give one nested stream per element (a stream of streams) instead of merging them.
What happens to the inner streams that flatMap produces, and why does that matter for resources like Files.lines?
basics
~20 sflatMap consumes each inner stream it creates and closes it when done. That matters because some streams hold resources — like Files.lines opening a file — so flatMap closing them after consumption helps avoid leaking file handles.
What is Stream.mapMulti (Java 16), and when would you prefer it over flatMap?
basics
~20 smapMulti is a Java 16 stream method that, like flatMap, lets each element produce zero or more outputs — but instead of returning a stream, you push results into a consumer. It avoids creating a small stream per element, so it can be faster and allocate less when each element yields few results.
What does it mean that a stream is 'consumed' after a terminal operation, and what happens if you reuse it?
basics
~10 sAfter you call a terminal operation, the stream is used up. Calling another operation on the same stream throws IllegalStateException. To process the data again you must create a brand-new stream from the source.
What is a terminal operation in the Java Streams API, and how does it differ from an intermediate operation?
basics
~20 sA terminal operation ends a stream and produces a result or side effect, like collect, forEach, count, or reduce. Intermediate operations (map, filter) just describe steps and return another stream. Only a terminal operation actually runs the pipeline.
Compare reduce and collect: when would you use each, and why is collect preferred for building mutable containers?
basics
~20 sreduce folds elements into one immutable value (like a sum) using a combining function. collect accumulates elements into a mutable container (List, Map, String) using a Collector. Use collect for building collections because it reuses one container instead of creating new ones each step.
How do min/max, count, and toArray behave as terminal operations — including the Comparator requirement, the Optional return, and the generator form of toArray?
basics
~20 smin and max need a Comparator and return an Optional (empty if the stream is empty). count returns the number of elements as a long. toArray() returns an Object[]; use toArray(String[]::new) to get a typed array of the right element type.
Explain short-circuiting terminal operations (anyMatch/allMatch/noneMatch, findFirst/findAny) and the edge cases of the match operations on an empty stream.
basics
~20 sSome terminal ops can stop early once the answer is known. anyMatch returns true as soon as one element matches; allMatch/noneMatch stop at the first counterexample; findFirst/findAny return one element and stop. On an empty stream: anyMatch is false, allMatch and noneMatch are both true.
What are the three overloaded forms of Stream.reduce, and what does each return?
basics
~20 sreduce folds a stream into one result. The one-arg form (just a combine function) returns an Optional because the stream may be empty. The two-arg form takes a starting value plus a combine function and returns a plain value. The three-arg form adds a combiner used when running in parallel.
What correctness laws must identity, accumulator, and combiner satisfy for reduce to give correct (and parallel-safe) results?
basics
~20 sThe identity must be a neutral value: combining it with any element leaves the element unchanged (like 0 for addition). The accumulator must be associative, so the order of grouping doesn't change the answer. And the combiner must give the same result as the accumulator. Break these and parallel results become wrong or nondeterministic.
How does reduce behave on an empty stream across its three forms, and how should you handle the Optional result?
basics
~20 sOn an empty stream the one-arg reduce returns an empty Optional, while the two-arg and three-arg forms return the identity you gave. For the Optional, don't call get() blindly — use orElse, orElseGet, ifPresent, or map so an empty result is handled safely.
When should you use reduce versus collect for a reduction, and why?
basics
~20 sUse reduce when you combine values into a new immutable result, like summing numbers or finding a max. Use collect when you accumulate into a mutable container, like building a List, Map, or StringBuilder. reduce makes a fresh value each step; collect mutates one container, which is far more efficient for large results.
How does reduce decompose a parallel stream, and what makes a reduction a good or bad candidate for parallelization?
basics
~20 sIn a parallel stream, reduce splits the data into chunks, reduces each chunk to a partial result, then merges the partials with the combiner. It pays off only when the data is large, splitting is cheap, the accumulator and combiner are fast and law-abiding, and there's no shared mutable state or boxing overhead.
What is mutable reduction in the Java Streams API, and how does collect() perform it?
basics
~20 sMutable reduction combines stream elements by adding them into one mutable container (like a List or StringBuilder) instead of creating a new value each step. collect() does this: it makes a container, then drops each element into it.
When would you use the three-argument collect(supplier, accumulator, combiner) instead of collect(Collector), and how do the two forms relate?
basics
~20 sThe three-arg collect lets you spell out the container, how to add an element, and how to merge two containers, all inline. collect(Collector) is the packaged version using ready-made collectors like Collectors.toList(). Prefer the packaged ones; use three-arg for quick one-off containers.
What are the three Collector characteristics (CONCURRENT, UNORDERED, IDENTITY_FINISH), and how does each affect parallel collection?
basics
~20 sThey are hints a Collector gives the stream. CONCURRENT means all threads can safely share one container. UNORDERED means element order doesn't matter so the stream can skip preserving it. IDENTITY_FINISH means the accumulation container is already the final result, so the finisher can be skipped.
What contract must the supplier, accumulator, and combiner of a collect() satisfy for a correct parallel result, and what goes wrong if it is violated?
basics
~20 sThe combiner must produce the same result as accumulating everything into one container. The functions must not interfere with the source or share state badly. If they disagree, a parallel stream gives wrong or random results, even though the sequential version looks fine.
When should you choose collect() over reduce() (and vice versa), and how does parallel performance factor in?
basics
~20 sUse reduce() when combining gives a single immutable value (a sum, max, the smallest object) and combining is cheap. Use collect() when the result is a mutable container (List, Map, String). collect avoids copying a growing container per element, so it scales; reduce on a container would be O(n^2).
How do you collect a Stream into a List, and what is the difference between Collectors.toList(), Collectors.toUnmodifiableList(), and Stream.toList()?
basics
~10 sUse stream.collect(Collectors.toList()) to get a List. toUnmodifiableList() gives a List you cannot change (add/remove throws). Since Java 16, stream.toList() is a shorter way to get an unmodifiable list.
How does Collectors.toMap(keyMapper, valueMapper) work, and what does each function receive and produce?
basics
~20 stoMap takes two functions: a keyMapper that turns each element into the map key, and a valueMapper that turns each element into the value. The result is a Map built from those keys and values.
Collectors.toMap throws IllegalStateException on duplicate keys. How do you resolve that, and what does the 3-argument merge-function overload do?
basics
~20 sAdd a third argument: a merge function. When two elements produce the same key, the merge function takes the existing value and the new value and returns the one to keep. For example (a, b) -> b keeps the last value.
How do you control which concrete Map type Collectors.toMap returns (e.g. a TreeMap or LinkedHashMap), and what does the 4-argument Supplier overload require?
basics
~10 sUse the four-argument toMap(keyMapper, valueMapper, mergeFunction, mapSupplier). The fourth argument is a supplier like TreeMap::new that creates the map you want. You must also pass a merge function with this overload.
Compare the mutability, null-handling, and concurrency guarantees across the Collectors collection/map factories (toList/toSet/toMap vs their unmodifiable and concurrent variants). When does the choice actually matter?
basics
~10 sThe plain toList/toSet/toMap give a mutable result of unspecified type and mostly tolerate nulls (except toMap values). The toUnmodifiable* variants give immutable results and reject nulls. toConcurrentMap supports thread-safe parallel collection.
What does Collectors.groupingBy do, and what is the shape of the result?
basics
~20 sgroupingBy takes a function that maps each element to a key. It returns a Map where each key points to a list of all the elements that produced that key, like sorting items into labeled buckets.
How do you count or aggregate elements per group instead of collecting lists? Explain the downstream collector.
basics
~10 sUse the two-argument groupingBy(classifier, downstream). The downstream collector decides what each group becomes. For example groupingBy(Person::city, Collectors.counting()) gives Map<String, Long> of how many people are in each city.
What is Collectors.partitioningBy and how does it differ from groupingBy?
basics
~20 spartitioningBy splits elements into exactly two groups using a true/false test. The result is Map<Boolean, List<T>> with keys true and false. groupingBy can make any number of groups from any key; partitioningBy is the special two-bucket case.
How do you produce a multi-level grouping such as Map<Country, Map<City, Long>>, and how do nested downstream collectors compose?
basics
~10 sUse groupingBy whose downstream is another groupingBy. The outer classifier picks the first key, the inner groupingBy groups within each outer group, and an innermost collector like counting() produces the leaf value.
What guarantees does groupingBy make about result mutability, ordering, null handling, and parallel execution, and when would you reach for groupingByConcurrent?
basics
~20 sBy default groupingBy gives a plain HashMap with ArrayList values, in no guaranteed order, and a null classifier key throws. For parallel streams, groupingByConcurrent can build a ConcurrentHashMap directly, but only helps with an unordered, truly parallel stream.
What is a downstream collector, and how do you use one to count or sum the elements within each group of a groupingBy?
basics
~20 sgroupingBy can take a second collector that runs on each group. So instead of getting lists, you can count items per group with Collectors.counting() or add them up with summingInt(). The result is a Map from key to the computed value.
How does Collectors.joining work, including its delimiter, prefix, and suffix overloads, and when is it preferable to manual string concatenation in a stream?
basics
~20 sCollectors.joining() concatenates the stream's CharSequence elements into one String. Overloads let you add a separator between elements, plus an optional prefix and suffix, e.g. joining(", ", "[", "]") turns names into "[a, b, c]". It only works on CharSequence elements, so map to String first.
Explain Collectors.collectingAndThen and Collectors.teeing, and give a realistic use for each.
basics
~20 scollectingAndThen wraps a collector and runs a final function on its result, e.g. collect into a List then make it unmodifiable. teeing (Java 12+) feeds the stream into two collectors at once and merges their two results with a combiner function, e.g. compute average as sum and count in one pass.
What are the mapping, flatMapping, and filtering collector adapters, and how do they differ from the Stream operations of the same name?
basics
~20 sThey are adapter collectors that transform or filter elements before handing them to another (downstream) collector. mapping changes each element, flatMapping expands each into many, and filtering drops some. They are used inside groupingBy where you can't easily put a Stream.map/filter, because the grouping already routed the elements.
How do you implement a custom Collector, and what are the roles of supplier, accumulator, combiner, finisher, and characteristics?
basics
~20 sA Collector has four functions plus a set of flags. The supplier makes a fresh mutable container, the accumulator adds one element into it, the combiner merges two containers (for parallel streams), the finisher turns the container into the final result, and characteristics tell the runtime if it can skip the finisher, run unordered, or run concurrently. You build one with Collector.of(...).
Why do IntStream, LongStream, and DoubleStream exist when you already have Stream<Integer>? What problem do they solve?
basics
~20 sA Stream<Integer> stores each number as a wrapped Integer object, which costs extra memory and time. Primitive streams (IntStream, LongStream, DoubleStream) hold raw int/long/double values directly, avoiding that wrapping and giving handy numeric operations like sum() and average().
How do IntStream.range and IntStream.rangeClosed differ, and when would you use each?
basics
~10 sBoth generate a stream of consecutive ints. range(start, end) excludes the end value; rangeClosed(start, end) includes it. So range(1, 5) gives 1,2,3,4 and rangeClosed(1, 5) gives 1,2,3,4,5.
Explain boxed(), mapToObj, and the asLongStream()/asDoubleStream() conversions. How do you move between primitive streams and object streams?
basics
~20 sboxed() turns an IntStream into a Stream<Integer> by wrapping each value, so you can collect into a List. mapToObj turns each primitive into any object you choose. asLongStream()/asDoubleStream() widen an IntStream to a LongStream/DoubleStream. To go from objects to primitives, use mapToInt/mapToLong/mapToDouble.
What does summaryStatistics() return on a primitive stream, and why might you prefer it over calling sum(), min(), max(), and average() separately?
basics
~20 ssummaryStatistics() returns one object (e.g. IntSummaryStatistics) holding the count, sum, min, max, and average computed in a single pass over the stream. Calling sum(), min(), max(), average() separately would consume the stream multiple times — but a stream can only be consumed once.
Why do IntStream.min(), average(), etc. return OptionalInt/OptionalDouble instead of Optional<Integer> or a plain value?
basics
~20 sA stream might be empty, so there may be no minimum or average to return. Optional represents 'maybe a value'. The primitive variants OptionalInt/OptionalDouble exist so the result stays unboxed — Optional<Integer> would force a wrapper object, defeating the point of using a primitive stream.
What is a parallel stream in Java, and how do you create one from a collection?
basics
~20 sA parallel stream splits the data into chunks and processes them on multiple threads at once, instead of one element at a time. You create one with collection.parallelStream() or by calling .parallel() on an existing stream.
Where do parallel streams run their work by default, and why does the shared common ForkJoinPool matter?
basics
~20 sBy default parallel streams run on a single thread pool shared by the whole JVM, called the common ForkJoinPool. Its size is about the number of CPU cores minus one. Because it is shared, a slow or blocking task can starve everything else using it.
How does a parallel stream divide its source for parallel processing, and why does the source's data structure affect performance?
basics
~20 sA parallel stream uses a Spliterator, whose trySplit() repeatedly cuts the source into halves so different threads can process them. Sources that split cheaply and evenly — arrays and ArrayList — parallelize well; LinkedList and Stream.iterate split poorly, so parallelism barely helps.
How do you decide whether parallelism will actually pay off, and how do ordering-sensitive operations like findAny and forEach behave in parallel?
basics
~20 sParallelism pays off when there are many elements (N) and each costs real work (Q) — a big N×Q. For tiny data or cheap operations the overhead loses. In parallel, findAny may return any matching element (not the first), and forEach runs in no guaranteed order; use findFirst or forEachOrdered if order matters.
What happens if you operate on a Java stream after it has already been consumed, and why is a stream single-use?
basics
~10 sA stream can only be used once. After a terminal operation like collect() or forEach() runs, the stream is consumed, and touching it again throws IllegalStateException. Create a fresh stream from the source instead.
Why does modifying the source collection while streaming it cause a ConcurrentModificationException, and how do you restructure to avoid it?
basics
~20 sStreams read the source lazily through an iterator that detects changes. If you add or remove elements from the source collection while the stream is running, you usually get a ConcurrentModificationException. Build a new collection from the stream instead of editing the old one.
What goes wrong when Collectors.toMap encounters duplicate keys, and how do you handle it correctly?
basics
~10 sThe two-argument Collectors.toMap throws IllegalStateException ('Duplicate key') when two elements produce the same key. Use the three-argument version with a merge function to decide which value wins.
Why prefer IntStream/LongStream/DoubleStream over Stream<Integer> for numeric work, and what is the cost of getting it wrong?
basics
~10 sStream<Integer> wraps every number in an Integer object (boxing), which wastes memory and time. IntStream keeps raw int values, so it is faster and has handy methods like sum() and average().
Why are side effects in stream operations (and especially using peek for mutation) discouraged, and what is peek actually for?
basics
~20 sStreams expect each step to just transform data, not change outside state. peek is meant for debugging/logging only. Mutating in peek or map is fragile because steps are lazy and may be skipped, reordered, or run in parallel.