Definition
- Arrays.stream(arr) converts a String array into a stream.
- groupingBy(identity(), LinkedHashMap::new, counting()) builds a frequency map preserving array order.
- filter(count == 1).findFirst() returns the first string that appears exactly once.
- The pattern is identical to the first non-repeating character problem but operates on
Stringinstead ofCharacter. LinkedHashMapensures the "first" non-repeating string respects the original array order.
At National Bank of India, a batch of transaction types arrives: ["NEFT", "UPI", "NEFT", "RTGS", "UPI", "IMPS"]. The bank needs to identify the first transaction type that appears only once. RTGS and IMPS are both unique, but RTGS comes first in the batch. LinkedHashMap preserves that order.
Understanding the Concepts
How does the pattern work for strings?
The approach is identical to the character version. Build a frequency map with LinkedHashMap, filter entries with count 1, and findFirst.
package com.bank.util;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FirstUniqueStringDemo {
public static void main(String[] args) {
String[] txnTypes = {"NEFT", "UPI", "NEFT", "RTGS", "UPI", "IMPS"};
Optional<String> firstUnique = Arrays.stream(txnTypes)
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() == 1L)
.map(Map.Entry::getKey)
.findFirst();
System.out.println("First unique: " + firstUnique.orElse("none"));
}
}
First unique: RTGS
The frequency map is {NEFT=2, UPI=2, RTGS=1, IMPS=1}. Filtering count equal to 1 gives {RTGS=1, IMPS=1}. The findFirst returns RTGS because it appears first in the LinkedHashMap.
How does the O(n^2) alternative work?
// Simpler but O(n^2)
List<String> list = Arrays.asList(txnTypes);
Optional<String> simple = list.stream()
.filter(s -> Collections.frequency(list, s) == 1)
.findFirst();
Collections.frequency scans the entire list for each element, making this O(n^2). The groupingBy approach is O(n).
How is this different from distinct()?
| Operation | Result |
|-----------|--------|
| distinct() | Removes duplicates, keeps one copy of each |
| First non-repeating | Finds the first element that appears EXACTLY ONCE |
distinct() on ["NEFT", "UPI", "NEFT", "RTGS"] gives ["NEFT", "UPI", "RTGS"]. First non-repeating gives "RTGS" because NEFT and UPI both appear more than once.
What about case-insensitive matching?
// Case-insensitive: normalize to uppercase before counting
Optional<String> caseInsensitive = Arrays.stream(txnTypes)
.map(String::toUpperCase)
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() == 1L)
.map(Map.Entry::getKey)
.findFirst();
What are common mistakes?
WRONG: Using HashMap instead of LinkedHashMap.
WRONG: Confusing "first non-repeating" with "first distinct" (distinct keeps one copy of duplicates; non-repeating keeps only elements that appear exactly once).
What should a senior engineer know?
- The pattern is universal. It works for any type with proper
equalsandhashCode: characters, strings, integers, employee IDs, transaction references. - For very large arrays, the frequency map uses O(k) memory where k is the number of distinct strings. If the array is enormous and mostly unique, this could be significant.
- In a real banking system, "first non-repeating transaction type" might be used for anomaly detection: a transaction type that appears only once in a batch might be an error or a rare event worth investigating.
Visual Overview
["NEFT", "UPI", "NEFT", "RTGS", "UPI", "IMPS"]
│
▼
LinkedHashMap: {NEFT=2, UPI=2, RTGS=1, IMPS=1}
│
▼
filter(count == 1) → {RTGS=1, IMPS=1}
│
▼
findFirst() → "RTGS"
Key Takeaway
Finding the first non-repeating string in an array uses the same pattern as the character version: build a frequency map with LinkedHashMap, filter entries with count equal to 1, and findFirst. The pattern is universal and works for any type with proper equals and hashCode.
The LinkedHashMap supplier is the key ingredient that makes "first" deterministic. Without it, HashMap's random iteration order makes the result unpredictable.
At National Bank of India, a batch of transaction types contains duplicates. The groupingBy approach identifies RTGS as the first unique type in O(n) time, compared to the simpler but O(n^2) approach using Collections.frequency.
Remember one sentence: the LinkedHashMap frequency-map-then-filter pattern is the universal approach for finding the first non-repeating element in any collection.
---