string manipulation
algorithms
character frequency
programming challenge
data structures

Find the first un-repeated character in a string

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To find the first un-repeated character in a string, you need both frequency information and original order. That is why the standard solution uses two passes: one to count characters and one to scan in order for the first count of one. This approach is simple, fast, and avoids the quadratic slowdown that appears in naive implementations.

Use a Counting Pass Followed by an Order Pass

In Python, collections.Counter makes the counting step very concise.

python
1from collections import Counter
2
3
4def first_unique_char(text: str):
5    counts = Counter(text)
6
7    for ch in text:
8        if counts[ch] == 1:
9            return ch
10
11    return None
12
13
14print(first_unique_char("swiss"))   # w
15print(first_unique_char("aabbcc"))  # None

The first pass records how often each character appears. The second pass preserves the original string order and finds the first character whose frequency is exactly one.

Why a Naive Loop Becomes Slow

Many first attempts call count inside a loop.

python
1def slow_first_unique_char(text: str):
2    for ch in text:
3        if text.count(ch) == 1:
4            return ch
5    return None

This looks reasonable, but text.count(ch) scans the full string each time. If the string is long, the overall runtime becomes roughly O(n^2). The counting-pass approach is approximately O(n) and scales much better.

Returning the Index Instead of the Character

Some variations of the problem ask for the index of the first unique character rather than the character itself. The structure of the solution stays the same.

python
1from collections import Counter
2
3
4def first_unique_index(text: str) -> int:
5    counts = Counter(text)
6
7    for index, ch in enumerate(text):
8        if counts[ch] == 1:
9            return index
10
11    return -1
12
13
14print(first_unique_index("leetcode"))  # 0
15print(first_unique_index("aabb"))      # -1

The important point is that uniqueness alone is not enough. You also need the earliest position in the original sequence.

Clarify What Counts as the "Same" Character

Before writing the final version, decide whether the comparison should be case-sensitive and whether spaces or punctuation should count.

If the comparison should ignore case, normalize before counting.

python
1from collections import Counter
2
3
4def first_unique_char_case_insensitive(text: str):
5    normalized = text.lower()
6    counts = Counter(normalized)
7
8    for original, lowered in zip(text, normalized):
9        if counts[lowered] == 1:
10            return original
11
12    return None
13
14
15print(first_unique_char_case_insensitive("Swiss"))  # w

This preserves the original returned character while using a case-insensitive equality rule.

A Dictionary Version Works in Any Language

If a language or interview setting does not provide a built-in counter, use a normal dictionary or hash map.

python
1def first_unique_char_dict(text: str):
2    counts = {}
3
4    for ch in text:
5        counts[ch] = counts.get(ch, 0) + 1
6
7    for ch in text:
8        if counts[ch] == 1:
9            return ch
10
11    return None

This is the same algorithm expressed in a more universal way. The key idea is not the library function. The key idea is frequency first, order second.

Unicode and Real-World Text

For interview problems, a "character" is often treated as a simple code point. Real-world text can be more complicated, especially with combining marks or language-specific normalization rules. If your application processes user-visible text in many languages, define the normalization policy up front rather than assuming that one code point always equals one perceived character.

In many business applications, basic string iteration is still sufficient, but it is worth knowing the assumption you are making.

Common Pitfalls

The biggest pitfall is calling count inside a loop and accidentally creating a quadratic-time solution.

Another mistake is returning the first unique key from a dictionary rather than the first unique character in the original string order. Those are not always the same question in every language or implementation.

It is also easy to forget to define case sensitivity, whitespace rules, or punctuation rules before the function gets reused in multiple places.

Finally, be consistent about the failure value. Returning None in one helper and -1 in another can create awkward calling code.

Summary

  • The standard solution uses two passes: count first, then scan in original order.
  • 'Counter is a convenient Python tool, but a plain dictionary works too.'
  • Avoid count inside a loop because it turns the algorithm into O(n^2).
  • Decide early whether case, spaces, and punctuation affect equality.
  • Return a consistent sentinel value when no unique character exists.

Course illustration
Course illustration

All Rights Reserved.