Back to Stream: map & Transform
Q28Question 28 of 34

Find the first non-repeating character in a string using chars() and LinkedHashMap

Key Concepts
  • string.chars() returns an IntStream of character codes; mapToObj boxes each into a Character
  • groupingBy(c -> c, LinkedHashMap::new, counting()) counts characters preserving first-seen order
  • LinkedHashMap is mandatory: HashMap would scramble the order and 'first' would be meaningless
  • Stream the ordered entrySet, filter count == 1, findFirst() gives the first non-repeating character
  • The approach is O(n): one pass to count, one pass to scan the ordered map
  • Return Optional<Character> to handle the case where all characters repeat

Definition

Count chars into a LinkedHashMap preserving first-seen order, then pick the first entry with count 1.
DIAGRAM
- **string.chars()** returns an `IntStream` of character codes.
- **mapToObj(c -> (char) c)** boxes each into a `Character`.
- **groupingBy(c -> c, LinkedHashMap::new, counting())** counts preserving insertion order.
- **LinkedHashMap** is mandatory: HashMap would lose the order and 'first' would be arbitrary.
- **findFirst()** on the ordered entries returns `Optional<Character>` for the first non-repeating char.

Priya at the National Bank of India scans a reference code letter by letter, tallying how often each letter appears while remembering the order she first saw them. The first letter with a tally of one is the answer.

Understanding the Concepts

How do you find the first non-repeating character?

Count character frequencies in a LinkedHashMap (preserves first-seen order), then find the first entry with count 1.

JAVA
package com.bank.core;

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

public class FirstNonRepeating {
    public static void main(String[] args) {
        String input = "swiss";

        Optional<Character> result = input.chars()
            .mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(
                c -> c,
                LinkedHashMap::new,
                Collectors.counting()))
            .entrySet().stream()
            .filter(e -> e.getValue() == 1L)
            .map(Map.Entry::getKey)
            .findFirst();

        result.ifPresentOrElse(
            ch -> System.out.println("First non-repeating: '" + ch + "'"),
            () -> System.out.println("All characters repeat"));
    }
}
TEXT
First non-repeating: 'w'

Why is LinkedHashMap mandatory?

HashMap does not preserve insertion order, so the 'first' non-repeating character would be arbitrary.

| Map type | Preserves first-seen order? | Correct for this problem? |
|---|---|---|
| LinkedHashMap | Yes | Yes |
| HashMap | No | No |
| TreeMap | No (sorts by key) | No |

JAVA
// WRONG: HashMap loses order
Map<Character, Long> unordered = input.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
// Iterating this map gives an arbitrary character with count 1

// CORRECT: LinkedHashMap preserves first-seen order
Map<Character, Long> ordered = input.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        c -> c, LinkedHashMap::new, Collectors.counting()));

What about case sensitivity?

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

What about strings where all characters repeat?

The result is Optional.empty(), correctly representing 'no unique character found'.

JAVA
String allRepeat = "aabbcc";
// Result: Optional.empty()

What are common mistakes?

Mistake: using HashMap instead of LinkedHashMap.

JAVA
// WRONG: HashMap gives an arbitrary character, not the first one
GroupingBy(c -> c, Collectors.counting())  // default is HashMap

// CORRECT: explicitly specify LinkedHashMap
groupingBy(c -> c, LinkedHashMap::new, Collectors.counting())

What about Unicode characters beyond the BMP?

chars() iterates individual char values. For emoji or rare scripts, use codePoints() instead.

JAVA
// For surrogate pairs (emoji, etc.):
String emoji = "hello🌍world🌍";
emoji.codePoints()
    .mapToObj(cp -> new String(Character.toChars(cp)))
    .collect(Collectors.groupingBy(
        s -> s, LinkedHashMap::new, Collectors.counting()));

What should a senior engineer know?

  • LinkedHashMap is mandatory for correctness; this is the key insight interviewers test.
  • The algorithm is O(n): one pass to count, one pass to scan. Space is O(k) where k is the alphabet size.
  • Return Optional to honestly represent the case where no unique character exists.
  • Decide case sensitivity and whitespace handling up front; both change the answer.

What are common interview follow-up questions?

  • "How do you find the first repeating character?" Filter for count > 1 instead of count == 1.
  • "Can you solve it in one pass?" Yes, but it requires a manual linked structure, not a stream-friendly approach.
  • "How do you find the last non-repeating character?" Use reduce instead of findFirst to get the last match.

Visual Overview

DIAGRAM
  String: "swiss"
       │
       │  chars() → mapToObj
       ▼
  Stream<Character> ['s', 'w', 'i', 's', 's']
       │
       │  groupingBy(c, LinkedHashMap, counting())
       ▼
  LinkedHashMap (preserves first-seen order):
    's' → 3
    'w' → 1
    'i' → 1
       │
       │  filter(count == 1) → findFirst()
       ▼
  Optional['w']  ← first non-repeating character

Key Takeaway

To find the first non-repeating character, stream the string's characters, count them with groupingBy using a LinkedHashMap to preserve first-seen order, then find the first entry with count 1. LinkedHashMap is mandatory: without it, 'first' would be arbitrary because HashMap has no ordering guarantee.

For 'swiss', the LinkedHashMap preserves the order s, w, i. The first entry with count 1 is 'w'. For all-repeating strings like 'aabbcc', the result is correctly Optional.empty().

At the National Bank of India, this pattern was used in a reference-code validator. The algorithm is O(n) with one counting pass and one scan, and returning Optional honestly represents the no-unique-character case.

Remember one sentence: LinkedHashMap preserves first-seen order, which is what makes 'first non-repeating' meaningful, and always decide case sensitivity up front.

---

Share
Use ← → arrow keys to navigate