Back to Stream: map & Transform
Q4Question 4 of 34

Compute the total, average, minimum, and maximum age using a primitive IntStream via mapToInt

Key Concepts
  • `mapToInt(Employee::getAge)` turns a `Stream<Employee>` into a primitive `IntStream`, avoiding `Integer` boxing.
  • `IntStream` adds numeric terminal operations like `sum()`, `average()`, `min()`, `max()`, and `summaryStatistics()`.
  • `sum()` returns a plain `int`, but `average()`, `min()`, and `max()` return `OptionalDouble` or `OptionalInt` because the stream can be empty.
  • A primitive stream stores raw `int` values, so it avoids the memory and CPU cost of boxing and unboxing.
  • `summaryStatistics()` computes count, sum, min, average, and max in a single pass over the data.
  • Always handle the empty-stream case with `getAsDouble`/`orElse` rather than assuming a value is present.

Definition

`employees.stream().mapToInt(Employee::getAge)` produces an `IntStream` whose `sum()`, `average()`, `min()`, and `max()` compute age statistics directly on primitive ints without boxing.

A primitive stream is a specialized stream of int, long, or double values.

  • mapToInt – a method that maps each element to an int and returns an IntStream.
  • IntStream – a stream of raw int values with numeric operations that Stream does not have.
  • sum() – returns a primitive int (or long/double for the other primitive streams).
  • average(), min(), max() – return an OptionalDouble or OptionalInt, because an empty stream has no meaningful result.

Using a primitive stream for numeric work avoids boxing each number into an Integer object.

At National Bank of India, Priya needs the total, average, youngest, and oldest age of a team. A Stream is like asking every clerk to write each age on its own paper envelope before you can add them, then opening each envelope again to read the number. An IntStream is like reading the ages straight off a single tally sheet: no envelopes to seal and unseal. The envelopes are Integer boxing, and skipping them is exactly what mapToInt buys you.

Understanding the Concepts

What is mapToInt and why use it?

mapToInt is an intermediate operation that takes a ToIntFunction and returns an IntStream. Every element is converted to a primitive int, so the resulting stream holds raw numbers rather than Integer objects.

JAVA
package com.bank.hr;

import java.util.List;
import java.util.stream.IntStream;

public class AgeStats {
    public static IntStream ages(List<Employee> employees) {
        return employees.stream().mapToInt(Employee::getAge);
    }
}

The benefit is that IntStream carries numeric terminal operations that a Stream or a Stream does not expose directly.

How do you compute total, average, min, and max?

IntStream gives you sum(), average(), min(), and max(). Note that sum() returns an int, while the other three return optionals because the stream might be empty.

JAVA
package com.bank.hr;

import java.util.List;
import java.util.OptionalDouble;
import java.util.OptionalInt;

public class AgeReport {
    public static void main(String[] args) {
        List<Employee> team = List.of(
            new Employee(1L, "Rajesh", 35),
            new Employee(2L, "Priya", 29),
            new Employee(3L, "Pankaj", 42));

        int total = team.stream().mapToInt(Employee::getAge).sum();
        OptionalDouble average = team.stream().mapToInt(Employee::getAge).average();
        OptionalInt youngest = team.stream().mapToInt(Employee::getAge).min();
        OptionalInt oldest = team.stream().mapToInt(Employee::getAge).max();

        System.out.println("Total   = " + total);
        System.out.println("Average = " + average.getAsDouble());
        System.out.println("Min     = " + youngest.getAsInt());
        System.out.println("Max     = " + oldest.getAsInt());
    }
}
TEXT
Total   = 106
Average = 35.333333333333336
Min     = 29
Max     = 42

Why return OptionalDouble and OptionalInt instead of a plain number?

An empty stream has a well-defined sum of zero, but no defined average, minimum, or maximum. Returning an Optional forces the caller to handle the empty case instead of receiving a misleading default.

JAVA
// Empty team: sum is 0, but average/min/max have no value
List<Employee> empty = List.of();
int total = empty.stream().mapToInt(Employee::getAge).sum();        // 0
OptionalDouble avg = empty.stream().mapToInt(Employee::getAge).average(); // OptionalDouble.empty
System.out.println(total);               // 0
System.out.println(avg.isPresent());     // false
System.out.println(avg.orElse(0.0));     // 0.0, a value you chose

Why is IntStream preferred over Stream for numbers?

A Stream stores each number as a boxed Integer object on the heap. Every arithmetic step unboxes to int, computes, and reboxes. This costs extra memory and CPU. An IntStream keeps raw int values, so there is no boxing or unboxing in the pipeline.

| Aspect | Stream | IntStream |
|--------|-------------------|-------------|
| Element storage | boxed Integer objects | raw int values |
| Boxing per element | yes | no |
| Numeric ops | via reduce/mapToInt | built in: sum, average, min, max |
| Memory footprint | higher (object headers) | lower (primitives) |
| Typical use | general objects | numeric aggregation |

JAVA
// Boxed: every age becomes an Integer, then reduce unboxes to add
int totalBoxed = team.stream()
    .map(Employee::getAge)      // Stream<Integer>
    .reduce(0, Integer::sum);

// Primitive: no boxing, direct sum
int totalPrimitive = team.stream()
    .mapToInt(Employee::getAge) // IntStream
    .sum();

How does summaryStatistics() compute everything in one pass?

Calling sum(), average(), min(), and max() separately traverses the data four times. summaryStatistics() returns an IntSummaryStatistics holding count, sum, min, average, and max after a single pass.

JAVA
package com.bank.hr;

import java.util.IntSummaryStatistics;
import java.util.List;

public class OnePassStats {
    public static void main(String[] args) {
        List<Employee> team = List.of(
            new Employee(1L, "Rajesh", 35),
            new Employee(2L, "Priya", 29),
            new Employee(3L, "Pankaj", 42));

        IntSummaryStatistics stats = team.stream()
            .mapToInt(Employee::getAge)
            .summaryStatistics();

        System.out.println("Count   = " + stats.getCount());
        System.out.println("Sum     = " + stats.getSum());
        System.out.println("Min     = " + stats.getMin());
        System.out.println("Average = " + stats.getAverage());
        System.out.println("Max     = " + stats.getMax());
    }
}
TEXT
Count   = 3
Sum     = 106
Min     = 29
Average = 35.333333333333336
Max     = 42

How do you go back and forth between primitive and object streams?

Use mapToInt to drop into a primitive stream and boxed() to return to a Stream when you need object-based operations such as collect(toList()).

JAVA
// Object -> primitive -> object
List<Integer> ages = team.stream()
    .mapToInt(Employee::getAge) // IntStream
    .boxed()                    // Stream<Integer>
    .collect(java.util.stream.Collectors.toList());

What are the common mistakes?

The first mistake is calling getAsDouble or getAsInt on an optional without checking whether the stream was empty, which throws NoSuchElementException.

JAVA
// WRONG: throws NoSuchElementException on an empty team
double avg = empty.stream().mapToInt(Employee::getAge).average().getAsDouble();
JAVA
// CORRECT: supply a default for the empty case
double avg = empty.stream().mapToInt(Employee::getAge).average().orElse(0.0);

The second mistake is doing numeric work with boxed streams and reduce when IntStream already provides the operation.

JAVA
// WRONG style: boxing then manual reduce for a simple sum
int total = team.stream().map(Employee::getAge).reduce(0, Integer::sum);

// CORRECT: primitive stream with sum()
int total = team.stream().mapToInt(Employee::getAge).sum();

What are the edge cases around empty streams and overflow?

An empty IntStream returns 0 from sum() and empty optionals from the others. Also, sum() on an IntStream returns an int and can overflow for very large totals; use mapToLong for sums that may exceed Integer.MAX_VALUE.

JAVA
// Overflow risk: many large ints summed as int can wrap around
long safeTotal = transactions.stream()
    .mapToLong(t -> t.getAmountInPaise()) // LongStream
    .sum();

When should you use a primitive stream versus an object stream?

Use a primitive stream whenever the pipeline is fundamentally numeric: summing balances, averaging ages, finding the largest transaction. Use an object stream when you need to keep the full object, group, or collect into object-based collections.

JAVA
// Numeric aggregation: primitive stream
double averageAge = team.stream().mapToInt(Employee::getAge).average().orElse(0);

// Keep objects: object stream
List<Employee> seniors = team.stream()
    .filter(e -> e.getAge() >= 40)
    .collect(java.util.stream.Collectors.toList());

What should a senior engineer know?

  • Three primitive streams. IntStream, LongStream, and DoubleStream exist to avoid boxing for the three numeric primitives; there is no CharStream or ByteStream.
  • Money is special. For currency, ages are fine as int, but money amounts belong in BigDecimal; primitive streams of double risk rounding errors, so sum money as BigDecimal with reduce.
  • One pass with statistics. Prefer summaryStatistics() over calling four terminal operations separately to avoid traversing the source multiple times.
  • Parallelism. IntStream.sum() is associative and parallelizes cleanly; average() internally tracks count and sum so it also parallelizes correctly.

What are common interview follow-up questions?

  • "What does sum() return versus average()?" Answer: sum() returns a primitive int; average() returns an OptionalDouble.
  • "Why an Optional for average but not for sum?" Answer: an empty stream has sum zero but no defined average, min, or max.
  • "Why prefer IntStream over Stream?" Answer: it avoids boxing and unboxing and offers numeric operations directly.
  • "How do you compute all statistics in one pass?" Answer: use summaryStatistics() returning IntSummaryStatistics.
  • "How do you avoid overflow when summing?" Answer: use mapToLong so the sum is a long.

Visual Overview

DIAGRAM
  List<Employee>
        │
        │  stream()
        ▼
  Stream<Employee>
        │
        │  mapToInt(Employee::getAge)   <- drop boxing, go primitive
        ▼
  IntStream  [ 35, 29, 42 ]   (raw ints, no Integer objects)
        │
        ├── sum()        -> int            106
        ├── average()    -> OptionalDouble  35.33
        ├── min()        -> OptionalInt     29
        ├── max()        -> OptionalInt     42
        └── summaryStatistics() -> IntSummaryStatistics (all in one pass)
DIAGRAM
  Stream<Integer> (boxed path)        IntStream (primitive path)
  ┌───────────────────────────┐      ┌───────────────────────────┐
  │ [Integer] [Integer] [...]  │      │  35   29   42              │
  │  unbox -> add -> rebox     │      │  add directly, no boxing   │
  │  heap objects + GC         │      │  raw ints on the stack     │
  └───────────────────────────┘      └───────────────────────────┘
        slower, more memory                faster, less memory

Key Takeaway

When Priya needs the total, average, youngest, and oldest age of a team, the clean path is employees.stream().mapToInt(Employee::getAge). That single step turns a stream of employees into an IntStream of raw ages. Instead of wrapping each age in an Integer envelope and reopening it to do math, the ages sit on one tally sheet ready to be added.

On that IntStream you get numeric operations for free. sum() gives the total as a plain int. average(), min(), and max() give results too, but wrapped in OptionalDouble or OptionalInt. The reason is honesty: an empty team has a sum of zero, but it has no average, no youngest, and no oldest, so the API makes you handle that case with orElse instead of handing you a misleading default.

The reason a primitive stream beats Stream for numbers is boxing. A boxed stream stores each number as a heap object and unboxes and reboxes around every operation, costing memory and CPU. A primitive stream skips all of that. When you need every statistic at once, summaryStatistics() computes count, sum, min, average, and max in a single pass.

Two cautions carry over to production. Guard the optionals so an empty stream does not throw NoSuchElementException, and remember that sum() on an IntStream can overflow, so switch to mapToLong for large totals. And while ages are fine as ints, money always belongs in BigDecimal.

Remember one sentence: use mapToInt to get a primitive IntStream, where sum() returns an int and average/min/max return optionals you must unwrap safely.

---

Share
Use ← → arrow keys to navigate