Back to Stream Practice: Employee Analysis
Q26Question 26 of 43

Find employees who do NOT belong to any of the given departments (use !Set.contains)

Key Concepts
  • Put excluded values in a HashSet for O(1) contains checks instead of O(n) with a List
  • filter(e -> !excluded.contains(e.getDepartment())) keeps employees NOT in the excluded set
  • Build the Set once outside the stream, not inside the predicate, to avoid redundant construction
  • Predicate.not(method-ref) provides a cleaner negation syntax in some cases
  • Normalize case in both the Set and the compared value if department casing is inconsistent
  • Set.of() creates an immutable set for a fixed exclusion list; EnumSet is faster for enum departments

Definition

Build a Set of excluded departments and filter with the negated contains check to keep employees whose department is not in the set.
DIAGRAM
- **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?

JAVA
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()));
    }
}
TEXT
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 |

JAVA
// 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.

JAVA
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?

JAVA
// 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.

JAVA
// 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.

JAVA
// 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.

JAVA
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 EnumSet for 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 with partitioningBy to get both included and excluded lists.

Visual Overview

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate