Definition
- groupingBy(getSalary) produces
Mapbucketed 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:
equalschecks 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?
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() + ")"));
});
}
}
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.
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() + ")"));
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.
// 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.
// 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?
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
setScalebefore grouping. - "How do you find the salary shared by the most employees?"
max(comparingInt(e -> e.getValue().size()))on the bySalary entrySet.
Visual Overview
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.
---