Back to Stream Practice: Employee Analysis
Q37Question 37 of 43

Find pairs of employees who have the same salary

Key Concepts
  • groupingBy(getSalary) groups employees by salary amount into Map<BigDecimal, List<Employee>>
  • Filter groups with size > 1 to find salary values shared by multiple employees
  • For actual pairs, generate combinations from each group using nested iteration: N*(N-1)/2 pairs
  • BigDecimal.compareTo == 0 checks value equality, while equals also checks scale (1000000 != 1000000.00)
  • Ensure consistent BigDecimal scale before grouping so equals-based groupingBy works correctly
  • The groupBy approach is O(n) whereas comparing all pairs with nested loops is O(n^2)

Definition

Group employees by salary with `groupingBy(getSalary)`, then filter groups with more than one employee to find same-salary pairs.
  • groupingBy(getSalary) produces Map> bucketed by salary amount.
  • Filter size > 1 keeps salary groups where multiple employees earn the same amount.
  • Pair generation from each group of N employees produces N*(N-1)/2 unique pairs via nested iteration.
  • BigDecimal equality matters: equals checks both value and scale, so ensure consistent scale before grouping.
  • O(n) grouping is far more efficient than O(n^2) all-pairs comparison.

The payroll auditor at National Bank of India suspects some employees were given identical salaries by mistake. She groups all pay slips by the salary amount on each slip and flags any pile with more than one slip for review.

Understanding the Concepts

What does it mean to find same-salary pairs?

Finding same-salary pairs means identifying all combinations of two employees who earn exactly the same amount. The efficient approach groups by salary and then generates pairs within each group, rather than comparing every possible pair of employees.

How do you implement it with streams?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class SameSalaryPairs {

    record Employee(String id, String name, String department, BigDecimal salary) {}
    record EmployeePair(Employee a, Employee b) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("SAV2001", "Rajesh",  "TRADING",    new BigDecimal("1800000")),
            new Employee("SAV2002", "Priya",   "RISK",       new BigDecimal("1000000")),
            new Employee("SAV2003", "Kavita",  "OPERATIONS", new BigDecimal("1000000")),
            new Employee("SAV2004", "Deepak",  "RISK",       new BigDecimal("1500000")),
            new Employee("SAV2005", "Ananya",  "COMPLIANCE", new BigDecimal("900000")),
            new Employee("SAV2006", "Suresh",  "OPERATIONS", new BigDecimal("900000"))
        );

        // Step 1: Group by salary
        Map<BigDecimal, List<Employee>> bySalary = staff.stream()
            .collect(Collectors.groupingBy(Employee::salary));

        // Step 2: Filter for groups with 2+ employees
        bySalary.entrySet().stream()
            .filter(e -> e.getValue().size() > 1)
            .forEach(e -> {
                System.out.println("Rs." + e.getKey() + ":");
                e.getValue().forEach(emp ->
                    System.out.println("  " + emp.name() + " (" + emp.department() + ")"));
            });
    }
}
TEXT
Rs.1000000:
  Priya (RISK)
  Kavita (OPERATIONS)
Rs.900000:
  Ananya (COMPLIANCE)
  Suresh (OPERATIONS)

How do you generate actual pairs from each group?

For each group of N employees with the same salary, generate N*(N-1)/2 unique pairs using nested iteration.

JAVA
List<EmployeePair> pairs = bySalary.values().stream()
    .filter(group -> group.size() > 1)
    .flatMap(group -> {
        List<EmployeePair> p = new ArrayList<>();
        for (int i = 0; i < group.size(); i++) {
            for (int j = i + 1; j < group.size(); j++) {
                p.add(new EmployeePair(group.get(i), group.get(j)));
            }
        }
        return p.stream();
    })
    .toList();

pairs.forEach(pair -> System.out.println(
    pair.a().name() + " <-> " + pair.b().name()
    + " (Rs." + pair.a().salary() + ")"));
TEXT
Priya <-> Kavita (Rs.1000000)
Ananya <-> Suresh (Rs.900000)

How is groupBy different from nested-loop comparison?

| Aspect | groupBy approach | Nested-loop all-pairs |
|---|---|---|
| Time complexity | O(n) for grouping + O(k^2) per group | O(n^2) for all pairs |
| Space | Map of size d (distinct salaries) | None extra |
| Readability | Declarative, stream-based | Imperative, two nested loops |
| Scalability | Excellent for large n with few duplicates | Poor for large n |

What are common mistakes?

Mistake 1: Using BigDecimal.equals which also checks scale.

JAVA
// WRONG: equals checks both value AND scale
new BigDecimal("1000000").equals(new BigDecimal("1000000.00"));  // false!

// CORRECT: compareTo checks value only
new BigDecimal("1000000").compareTo(new BigDecimal("1000000.00")) == 0;  // true

// FIX for groupingBy: normalize scale before grouping
staff.stream().collect(Collectors.groupingBy(
    e -> e.salary().setScale(2, java.math.RoundingMode.HALF_UP)));

Mistake 2: Counting the same pair twice.

JAVA
// WRONG: (Priya, Kavita) and (Kavita, Priya) are the same pair
for (int i = 0; i < group.size(); i++)
    for (int j = 0; j < group.size(); j++)  // starts at 0, not i+1

// CORRECT: start inner loop at i+1 to avoid duplicates
for (int i = 0; i < group.size(); i++)
    for (int j = i + 1; j < group.size(); j++)

What about filtering pairs across different departments?

JAVA
List<EmployeePair> crossDeptPairs = pairs.stream()
    .filter(p -> !p.a().department().equals(p.b().department()))
    .toList();

// Shows same salary but different department: possible pay-band issue

What about thread safety and performance?

The groupingBy approach is safe for sequential streams. For parallel streams, the collector handles thread safety internally. The O(n) grouping dominates for large datasets, and the per-group pair generation is O(k^2) where k is the group size, which is typically small.

For very large datasets where even grouping is slow, consider a database query with GROUP BY salary HAVING COUNT(*) > 1 instead of in-memory streaming.

What are common interview follow-up questions?

  • "How do you find pairs in different departments?" Filter pairs where a.department() != b.department().
  • "What is the time complexity?" O(n) for grouping, O(k^2) for pair generation where k is the max group size.
  • "How do you handle BigDecimal scale differences?" Normalize with setScale before grouping.
  • "How do you find the salary shared by the most employees?" max(comparingInt(e -> e.getValue().size())) on the bySalary entrySet.

Visual Overview

DIAGRAM
  Input: [Rajesh 1.8M, Priya 1M, Kavita 1M, Deepak 1.5M, Ananya 0.9M, Suresh 0.9M]
       │
       │  groupingBy(salary)
       ▼
  ┌──────────────────────────────────────────┐
  │  Rs.1800K → [Rajesh]             (1) ✗  │
  │  Rs.1500K → [Deepak]             (1) ✗  │
  │  Rs.1000K → [Priya, Kavita]      (2) ✓  │
  │  Rs.900K  → [Ananya, Suresh]     (2) ✓  │
  └──────────────────────────────────────────┘
       │
       │  filter(size > 1)  →  generate pairs
       ▼
  Pairs: (Priya, Kavita), (Ananya, Suresh)

Key Takeaway

Finding same-salary pairs groups employees by salary amount using groupingBy and then filters for groups with more than one member. Within each matching group, nested iteration with the inner loop starting at i + 1 generates unique pairs without duplicates.

At the National Bank of India, the payroll auditor uses this pattern to flag employees who may have been assigned identical salaries by mistake. The groupBy approach is O(n) for grouping, far more efficient than comparing all possible pairs in O(n^2). The per-group pair generation is O(k^2) where k is the group size, but k is typically small.

The critical subtlety is BigDecimal.equals versus compareTo. The equals method checks both value and scale, so 1000000 and 1000000.00 are not equal by equals but are equal by compareTo. Since groupingBy uses equals internally, you must normalize the scale of salary values before grouping, or the same pay amount could end up in separate buckets.

Remember one sentence: groupBy(salary) then filter(size > 1) finds employees earning the same amount, and nested iteration within each group generates the actual pairs.

---

Share
Use ← → arrow keys to navigate