Definition
- **Set<String> excluded** holds the departments to exclude, using `HashSet` for O(1) `contains` lookups. - **filter(e -> !excluded.contains(e.getDepartment()))** keeps elements whose department is absent from the set. - **Predicate.not()** (Java 11+) provides a cleaner negation: `Predicate.not(e -> excluded.contains(e.getDepartment()))`. - **Build once**: create the Set outside the stream; do not rebuild it inside the predicate for every element. - **Case normalization**: if department strings have inconsistent casing, normalize both the Set contents and the compared value.
The National Bank of India HR manager has a blacklist of back-office departments pinned to the wall: IT and HR. She walks through the employee stack and checks each person's department against the pinned list. If the department is on the list, skip the card. If not, keep it. The pinned list is the Set, and the skip-or-keep logic is the negated contains predicate.
Understanding the Concepts
What does this question ask?
The interviewer wants you to filter a stream using a negated membership check against a collection of values. This tests your understanding of Set.contains, negation in predicates, and the performance difference between Set and List for lookups.
How do you filter employees not in a set of departments?
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class ExcludeDepartments {
public static void main(String[] args) {
List<Employee> employees = Employee.getSampleEmployees();
Set<String> excluded = Set.of("IT", "HR");
List<Employee> result = employees.stream()
.filter(e -> !excluded.contains(e.getDepartment()))
.toList();
result.forEach(e ->
System.out.println(e.getName() + " - " + e.getDepartment()));
}
}
Rajesh - TRADING
Priya - RISK
Amit - RISK
Kavita - OPERATIONS
Suresh - OPERATIONS
Deepak - COMPLIANCE
The !excluded.contains(dept) predicate is true when the department is NOT in the exclusion set.
Why use Set instead of List for the exclusion check?
| Operation | HashSet | ArrayList |
|---|---|---|
| contains() time | O(1) average | O(n) linear scan |
| 10 excluded depts, 10000 employees | 10000 hash lookups | 10000 x 10 comparisons |
| Memory | Slightly more (hash table overhead) | Slightly less (array) |
| Best for | Membership checks | Indexed access |
// WRONG: List.contains scans linearly
List<String> excludedList = List.of("IT", "HR", "ADMIN", "FACILITIES");
List<Employee> slow = employees.stream()
.filter(e -> !excludedList.contains(e.getDepartment())) // O(n) per check
.toList();
// CORRECT: Set.contains is O(1) average
Set<String> excludedSet = Set.of("IT", "HR", "ADMIN", "FACILITIES");
List<Employee> fast = employees.stream()
.filter(e -> !excludedSet.contains(e.getDepartment())) // O(1) per check
.toList();
How do you use Predicate.not() for cleaner negation?
Java 11 added Predicate.not() which can make negations more readable, though for this pattern the lambda is usually clearer.
import java.util.function.Predicate;
// Lambda negation (most common)
List<Employee> lambda = employees.stream()
.filter(e -> !excluded.contains(e.getDepartment()))
.toList();
// Predicate.not with a named predicate
Predicate<Employee> inExcluded = e -> excluded.contains(e.getDepartment());
List<Employee> negated = employees.stream()
.filter(Predicate.not(inExcluded))
.toList();
// negate() instance method
List<Employee> negate = employees.stream()
.filter(inExcluded.negate())
.toList();
How do you handle case-insensitive department matching?
// Build the exclusion set with normalized case
Set<String> excludedUpper = Set.of("IT", "HR");
// Normalize the compared value to match
List<Employee> caseInsensitive = employees.stream()
.filter(e -> !excludedUpper.contains(
e.getDepartment().toUpperCase()))
.toList();
What are common mistakes?
Mistake 1: building the Set inside the predicate.
// WRONG: creates a new Set for every employee!
List<Employee> wrong = employees.stream()
.filter(e -> !Set.of("IT", "HR").contains(e.getDepartment()))
.toList();
// CORRECT: build once outside
Set<String> excluded = Set.of("IT", "HR");
List<Employee> correct = employees.stream()
.filter(e -> !excluded.contains(e.getDepartment()))
.toList();
Mistake 2: using equals instead of contains for multiple values.
// WRONG: only checks one department, misses the other
List<Employee> wrong = employees.stream()
.filter(e -> !e.getDepartment().equals("IT")
&& !e.getDepartment().equals("HR"))
.toList();
// Becomes unreadable with 5+ departments
// CORRECT: Set.contains handles any number of exclusions
Set<String> excluded = Set.of("IT", "HR", "ADMIN", "FACILITIES", "LEGAL");
List<Employee> correct = employees.stream()
.filter(e -> !excluded.contains(e.getDepartment()))
.toList();
What about including instead of excluding?
Flip the negation to filter for departments that ARE in the set.
Set<String> included = Set.of("TRADING", "RISK");
// Include: keep only TRADING and RISK
List<Employee> included = employees.stream()
.filter(e -> included.contains(e.getDepartment()))
.toList();
// Exclude: keep everything EXCEPT TRADING and RISK
List<Employee> excluded = employees.stream()
.filter(e -> !included.contains(e.getDepartment()))
.toList();
What are common interview follow-up questions?
- "What if departments is an enum?" Use
EnumSetfor faster membership checks. - "How do you exclude by multiple fields?" Create a composite key:
excluded.contains(e.getDepartment() + "|" + e.getCity()). - "What if the excluded set changes at runtime?" Use a
ConcurrentHashMap.newKeySet()for thread-safe mutation. - "How do you log which employees were excluded?" Use
peek(e -> log(e))before the filter, or partition withpartitioningByto get both included and excluded lists.
Visual Overview
Set<String> excluded = {"IT", "HR"}
Stream<Employee>
[Rajesh-TRADING, Priya-RISK, Amit-IT, Kavita-HR, Suresh-OPS]
│
│ .filter(e -> !excluded.contains(e.getDepartment()))
▼
┌────────────────────────────────────────────────┐
│ TRADING in {IT, HR}? NO → !false → KEEP │
│ RISK in {IT, HR}? NO → !false → KEEP │
│ IT in {IT, HR}? YES → !true → SKIP │
│ HR in {IT, HR}? YES → !true → SKIP │
│ OPS in {IT, HR}? NO → !false → KEEP │
└────────────────────────────────────────────────┘
│
▼
[Rajesh-TRADING, Priya-RISK, Suresh-OPS]
Key Takeaway
Excluding employees from specific departments uses a Set for O(1) membership checks and a negated contains predicate. Build the excluded Set once outside the stream: Set.of("IT", "HR"). The filter predicate e -> !excluded.contains(e.getDepartment()) keeps only employees whose department is absent from the set.
Using a Set instead of a List matters for performance. HashSet.contains is average O(1) because it hashes to the bucket directly, while List.contains is O(n) because it scans every element. For 10 excluded departments checked against 10,000 employees, the Set does 10,000 hash lookups while the List does 100,000 comparisons.
The most common mistake is building the Set inside the predicate lambda, which creates a new Set for every employee. Build it once and capture it in the closure. The second mistake is chaining multiple !equals checks instead of using Set.contains, which becomes unreadable beyond two or three values.
Remember one sentence: put excluded values in a Set for O(1) lookups and filter with negated contains to keep everything outside the set.
---