RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Java questions
TrickyJava#906 min readJul 11, 2026

Second highest salary using Streams.

practice
learning
Practice modeTest yourself instead of reading straight through

Hook: Interviewers love this one because it looks tiny, but it checks whether you notice duplicates, ordering, and empty results.

Question: How do you find the second highest salary using Streams in Java?

Answer: Turn the salaries into a stream, remove duplicate values, sort them from high to low, and take the second item. If there are fewer than two distinct salaries, return an empty result instead of forcing a value. In Streams, that usually means using map, distinct, sorted, skip, and findFirst.

Interview-Ready Answer: I’d stream the employees, map each one to salary, remove duplicate salaries with distinct(), sort descending, then skip the highest and read the next value. I’d return an Optional so the method stays safe when there are fewer than two distinct salaries. That gives the correct second highest distinct salary and avoids the common duplicate-value bug.

🧠 Memory Map
Memory map — visual summary of this topic

What this really means

Detailed Explanation: In interview language, second highest salary usually means the second highest distinct salary. If two employees both earn 120000, that still counts as one top salary, not two positions. That small word, distinct, is the whole trap.

How the stream pipeline works under the hood

  1. Start with employees.stream(). A stream is just a pipeline for processing data in stages.
  2. Use map(Employee::getSalary) to extract only salaries. This changes each employee object into a salary value.
  3. Use distinct() to remove duplicates. Stateful means it remembers what it has already seen, so repeated salaries are ignored.
  4. Use sorted(Comparator.reverseOrder()) to put salaries in descending order, highest first.
  5. Use skip(1) to move past the highest salary.
  6. Use findFirst() to take the next one. The result is an Optional because there may not be a second distinct salary.

One important detail: sorted() is a terminal-heavy step in practice because it must collect and order all elements before it can hand out results. That is why this solution is clean, but not the most memory-friendly option for huge lists.

When to use this approach

Use this stream style when readability matters and the dataset is moderate, such as a few hundred or a few thousand rows. It is great in interview code because it shows you know the Stream API and can handle duplicates correctly. If the dataset is very large and performance is critical, a one-pass loop is faster because it avoids sorting.

Comparison with other approaches

ApproachIdeaTimeSpaceGood for
Stream pipelineDistinct, sort, skipO(n log n)O(n)Readable interview code
TreeSetKeep unique sorted valuesO(n log n)O(n)Neat alternative
One-pass loopTrack top twoO(n)O(1)Best performance

Performance note: Sorting 1 million salaries means a lot of comparisons and extra memory pressure. The stream version is still correct, but it is not the fastest possible answer. In a real system, you would choose the simplest correct version that fits the data size.

Edge cases to mention out loud

  • Duplicate top salaries: must use distinct().
  • Only one distinct salary: return empty, not a fake answer.
  • Null employee objects or null salaries: filter them or fail fast.
  • If salary can exceed int, use long or BigDecimal.

Think of it like a podium: first remove duplicate gold medals, then take the silver medal. That mental picture keeps you from accidentally returning the same top salary twice.

Real-World Story: Imagine a payroll dashboard in a fintech company that shows the top salaries by department. The report runs every night and feeds an executive summary email.

The team once wrote a stream pipeline without distinct(). In the sales department, two senior managers had the same highest salary, so the dashboard showed the second highest salary as the same number as the highest. Nobody noticed at first because the result looked believable.

What went wrong: the compensation review page started showing duplicate top values, and HR questioned whether the data was corrupted. The symptom was subtle: no crash, just a wrong number in a report. Logs were clean, but users saw a suspicious ranking that made audits and bonus planning harder. The fix was to treat the problem as a distinct salary question, not a simple sorted-list question.

Java
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class Main {
    static final class Employee {
        private final String name;
        private final Integer salary;

        Employee(String name, Integer salary) {
            this.name = name;
            this.salary = salary;
        }

        String getName() {
            return name;
        }

        Integer getSalary() {
            return salary;
        }

        @Override
        public String toString() {
            return name + ":" + salary;
        }
    }

    // We return Optional<Integer> because there may be fewer than two distinct salaries.
    static Optional<Integer> secondHighestSalary(List<Employee> employees) {
        Objects.requireNonNull(employees, "employees must not be null");

        return employees.stream()
                .filter(Objects::nonNull)        // Ignore null employee records instead of failing mid-stream.
                .map(Employee::getSalary)
                .filter(Objects::nonNull)        // Null salary values are not valid for ranking.
                .distinct()                      // Critical: second highest means second DISTINCT salary.
                .sorted(Comparator.reverseOrder())
                .skip(1)                         // Skip the highest distinct salary.
                .findFirst();                    // May be empty if only one distinct salary exists.
    }

    public static void main(String[] args) {
        List<Employee> team = List.of(
                new Employee("Asha", 120000),
                new Employee("Ben", 95000),
                new Employee("Cara", 120000),
                new Employee("Dev", 110000),
                new Employee("Eli", 95000)
        );

        System.out.println("Team: " + team);
        System.out.println("Second highest distinct salary: " + secondHighestSalary(team).orElse(null));

        // Edge case: all salaries are the same, so there is no second distinct salary.
        List<Employee> sameSalaryTeam = List.of(
                new Employee("Nina", 50000),
                new Employee("Omar", 50000)
        );

        Optional<Integer> result = secondHighestSalary(sameSalaryTeam);
        System.out.println("All same salaries present? " + result.isPresent());
        System.out.println("Fallback value if missing: " + result.orElse(-1));

        // Edge case: empty list also returns empty rather than throwing or guessing.
        System.out.println("Empty list result: " + secondHighestSalary(List.of()).orElse(null));
    }
}

Follow-up & Tricky Questions:

  • How would you do it without sorting? Keep track of the highest and second highest distinct values in one pass. That is O(n) time and O(1) space, but it is a little less expressive than the stream pipeline.
  • How do you get the second highest salary per department? First group employees by department, then apply the same distinct-and-sort logic inside each group. In Streams, that usually means groupingBy plus a downstream ranking step.
  • Why return Optional instead of null? Optional makes the absence of a second salary explicit and safer. It tells the caller, very clearly, that the result may not exist.
  • What if salaries are BigDecimal? You can still stream them, but compare with compareTo or a proper comparator. Never use subtraction for money values because precision matters.
  • Can you use max twice? Yes, but only if you first remove the highest salary from the data set or keep two running maxima. Otherwise, duplicates can make the second result equal the first.
  • Is findFirst() always deterministic? On this ordered pipeline it is, because sorted() establishes order. If you switch to findAny() or lose ordering, the result can vary.
  • Tricky: Is the second highest salary just the second element after sorting? No. If the top salary appears twice, the second element is still the top salary. You need distinct() if the question means second highest distinct salary.
  • Tricky: What happens with one employee? You get an empty result, not the same salary twice. That is the correct behavior because there is no second distinct value.
  • Tricky: Should you filter null salaries or let them crash? In interview code, either is acceptable if you explain it. In production, I prefer failing fast for bad data or filtering only when null is a known valid input shape.

Common Mistakes:

  • Forgetting distinct(): then duplicate top salaries can make the answer wrong. Correction: remove duplicates before ranking.
  • Returning the second row after sorting: this fails when the highest salary repeats. Correction: ask for the second highest distinct salary, not the second record.
  • Using Integer as a forced answer: this hides missing data. Correction: use Optional or clearly documented fallback behavior.
  • Ignoring nulls silently: null employee objects or null salary fields can break the pipeline. Correction: filter them or validate input up front.

Memory Hook: Picture a winners’ podium: first remove duplicate gold medals, then take the silver medal. That is the whole mental model for second highest distinct salary.

Cheat Sheet:

  • map employees to salary values.
  • distinct removes duplicate salaries.
  • sorted(Comparator.reverseOrder()) puts highest first.
  • skip(1) ignores the top salary.
  • findFirst() returns an Optional.
  • Time: O(n log n); space: O(n).

Practice Tasks:

  • Write the same logic for a List<Integer> instead of employees.
  • Change the code to find the third highest distinct salary.
  • Rewrite it as a one-pass loop and compare readability with the stream version.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; public class Main { static final class Employee { private final String name; private final Integer salary; Employee(String name, Integer salary) { this.name = name; this.salary = salary; } String getName() { return name; } Integer getSalary() { return salary; } @Override public String toString() { return name + ":" + salary; } } // We return Optional<Integer> because there may be fewer than two distinct salaries. static Optional<Integer> secondHighestSalary(List<Employee> employees) { Objects.requireNonNull(employees, "employees must not be null"); return employees.stream() .filter(Objects::nonNull) // Ignore null employee records instead of failing mid-stream. .map(Employee::getSalary) .filter(Objects::nonNull) // Null salary values are not valid for ranking. .distinct() // Critical: second highest means second DISTINCT salary. .sorted(Comparator.reverseOrder()) .skip(1) // Skip the highest distinct salary. .findFirst(); // May be empty if only one distinct salary exists. } public static void main(String[] args) { List<Employee> team = List.of( new Employee("Asha", 120000), new Employee("Ben", 95000), new Employee("Cara", 120000), new Employee("Dev", 110000), new Employee("Eli", 95000) ); System.out.println("Team: " + team); System.out.println("Second highest distinct salary: " + secondHighestSalary(team).orElse(null)); // Edge case: all salaries are the same, so there is no second distinct salary. List<Employee> sameSalaryTeam = List.of( new Employee("Nina", 50000), new Employee("Omar", 50000) ); Optional<Integer> result = secondHighestSalary(sameSalaryTeam); System.out.println("All same salaries present? " + result.isPresent()); System.out.println("Fallback value if missing: " + result.orElse(-1)); // Edge case: empty list also returns empty rather than throwing or guessing. System.out.println("Empty list result: " + secondHighestSalary(List.of()).orElse(null)); } }