Back to Stream Practice: String & Character
Q1Question 1 of 11

First non-repeating character in a string using streams

Key Concepts
  • chars().mapToObj(c -> (char) c) converts a String to a Stream<Character> for processing
  • groupingBy(identity(), LinkedHashMap::new, counting()) builds a frequency map that preserves insertion order
  • LinkedHashMap is critical because HashMap does not guarantee iteration order
  • Filter entries with count equal to 1, then findFirst returns the first non-repeating character
  • Without LinkedHashMap, the first entry with count 1 might not be the first non-repeating character in the string
  • This two-step pattern (frequency map then filter) is one of the top stream interview questions

Definition

Build a character frequency map using groupingBy with LinkedHashMap to preserve insertion order, then filter entries with count equal to 1 and findFirst.
  • chars().mapToObj(c -> (char) c) converts a String into a Stream by streaming the int code points and casting to char.
  • groupingBy(identity(), LinkedHashMap::new, counting()) builds a frequency map where the key is the character, the value is its count, and LinkedHashMap preserves the order characters first appeared in the string.
  • Filter and findFirst streams the map entries, keeps those with count equal to 1, and returns the first one in insertion order.
  • LinkedHashMap is the critical ingredient: HashMap randomizes iteration order, so the "first" non-repeating character would be arbitrary.

At National Bank of India, an IFSC code validator checks each character of a code like "SBIN0001234". It counts how many times each character appears, preserving the order they were first seen. Then it scans the count list and picks the first character that appears only once. LinkedHashMap is like a checklist where entries stay in the order they were written.

Understanding the Concepts

What is a non-repeating character?

A non-repeating character is one that appears exactly once in the string. The "first" non-repeating character is the one that appears earliest in the string among all characters with count 1.

How do you build a character frequency map?

JAVA
package com.bank.util;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FirstNonRepeatingDemo {
    public static void main(String[] args) {
        String ifscCode = "SBIN0001234";

        // Step 1: character frequency map with insertion order
        Map<Character, Long> freq = ifscCode.chars()
            .mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(
                Function.identity(),
                LinkedHashMap::new,
                Collectors.counting()));

        System.out.println("Frequency: " + freq);
    }
}
TEXT
Frequency: {S=1, B=1, I=1, N=1, 0=2, 1=2, 2=1, 3=1, 4=1}

LinkedHashMap preserves the order: S first, B second, I third, and so on.

How do you find the first non-repeating character?

JAVA
// Step 2: first entry with count == 1
Optional<Character> firstNonRepeating = freq.entrySet().stream()
    .filter(e -> e.getValue() == 1L)
    .map(Map.Entry::getKey)
    .findFirst();

System.out.println("First non-repeating: " + firstNonRepeating.orElse('?'));
TEXT
First non-repeating: S

Why is LinkedHashMap critical?

WRONG: Using HashMap loses insertion order.

JAVA
// WRONG: HashMap -- iteration order is random
Map<Character, Long> wrongMap = ifscCode.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()));  // HashMap by default!

// Might iterate as {0=2, 1=2, S=1, 4=1, B=1, ...}
// findFirst could return '4' or 'B' instead of 'S'

CORRECT: LinkedHashMap preserves insertion order.

JAVA
// CORRECT: LinkedHashMap preserves character order from the string
Map<Character, Long> correctMap = ifscCode.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        Function.identity(),
        LinkedHashMap::new,       // preserves insertion order
        Collectors.counting()));

What about a more complex example?

JAVA
String word = "BANANA";
// B=1, A=3, N=2 (LinkedHashMap order: B, A, N)
// Filter count==1: only B
// First non-repeating: 'B'

String word2 = "STRESS";
// S=3, T=1, R=1, E=1 (LinkedHashMap order: S, T, R, E)
// Filter count==1: T, R, E
// First non-repeating: 'T' (first in string order)

How do you handle case-insensitive matching?

JAVA
// Case-insensitive: convert to lowercase first
Optional<Character> caseInsensitive = "RaJeSh".chars()
    .mapToObj(c -> Character.toLowerCase((char) c))
    .collect(Collectors.groupingBy(
        Function.identity(),
        LinkedHashMap::new,
        Collectors.counting()))
    .entrySet().stream()
    .filter(e -> e.getValue() == 1L)
    .map(Map.Entry::getKey)
    .findFirst();

What if there is no non-repeating character?

JAVA
String allRepeating = "AABBCC";
// A=2, B=2, C=2 -- no entry has count 1
// findFirst returns Optional.empty()
Optional<Character> result = /* same pattern */;
System.out.println(result.orElse('?'));  // prints '?'

What about using indexOf for an alternative approach?

JAVA
// Alternative: O(n^2) but simpler
Optional<Character> alt = ifscCode.chars()
    .mapToObj(c -> (char) c)
    .filter(c -> ifscCode.indexOf(c) == ifscCode.lastIndexOf(c))
    .findFirst();
// Works but O(n^2) because indexOf scans for each character

What should a senior engineer know?

  • The chars() method returns an IntStream, not a Stream. The mapToObj(c -> (char) c) step is necessary to box the int code points into Character objects.
  • For Unicode strings with characters outside the Basic Multilingual Plane (emoji, some CJK characters), use codePoints() instead of chars() to handle surrogate pairs correctly.
  • The frequency map approach is O(n) time and O(k) space where k is the number of distinct characters.
  • The LinkedHashMap trick is the exact "gotcha" interviewers test for. Mentioning it unprompted shows deep understanding.

Visual Overview

DIAGRAM
  "STRESS"
     │
     ▼
  chars().mapToObj(c -> (char) c)
  [S, T, R, E, S, S]
     │
     ▼
  groupingBy(identity(), LinkedHashMap, counting())
  ┌────────────────────────────┐
  │ S → 3  (first seen pos 0)  │
  │ T → 1  (first seen pos 1)  │
  │ R → 1  (first seen pos 2)  │
  │ E → 1  (first seen pos 3)  │
  └────────────┬───────────────┘
               │
               ▼
  filter(count == 1) → [T, R, E]
               │
               ▼
  findFirst() → 'T'

Key Takeaway

Finding the first non-repeating character uses a two-step stream approach. First, build a character frequency map using chars().mapToObj(c -> (char) c).collect(groupingBy(identity(), LinkedHashMap::new, counting())). The LinkedHashMap supplier preserves insertion order so the map iterates in the order characters first appeared in the string. Second, stream the map entries, filter for count equal to 1, and findFirst returns the first non-repeating character in string position order.

At National Bank of India, the IFSC validator processes "STRESS" and produces {S:3, T:1, R:1, E:1} in LinkedHashMap. Filtering count equal to 1 gives {T:1, R:1, E:1}. The findFirst returns 'T' because it appeared earliest in the string. With a regular HashMap, the iteration order is random, and you might get R or E instead of T.

This LinkedHashMap subtlety is exactly what interviewers test for. Mentioning it unprompted demonstrates deep understanding of both the Stream API and Java collections.

Remember one sentence: LinkedHashMap in the groupingBy supplier preserves character insertion order, which is what makes "first" deterministic.

---

Share
Use ← → arrow keys to navigate