Back to Stream Practice: String & Character
Q3Question 3 of 11

First non-repeating string in an array using streams

Key Concepts
  • Same pattern as first non-repeating character: groupingBy with LinkedHashMap and counting, then filter count == 1
  • LinkedHashMap preserves array order so findFirst returns the first unique string by position
  • The pattern is universal: works for chars, strings, integers, or any type with proper equals/hashCode
  • Alternative: filter(s -> Collections.frequency(list, s) == 1).findFirst() is simpler but O(n^2)
  • The groupingBy approach is O(n) time and O(k) space where k is the number of distinct strings
  • Returns Optional.empty() if all strings are repeated

Definition

Use the same LinkedHashMap frequency pattern as first non-repeating character, but on strings: groupingBy(identity(), LinkedHashMap::new, counting()), filter count equal to 1, findFirst.
  • 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 String instead of Character.
  • LinkedHashMap ensures 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.

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

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

JAVA
// 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 equals and hashCode: 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

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

---

Share
Use ← → arrow keys to navigate