Skip to main content

387. First Unique Character in a String

easyAsked at JPMorgan

Return the index of the first non-repeating character in a string. JPMorgan asks this on Software Engineer Programme phone screens as a frequency-map warm-up that primes for the streaming follow-up: 'now imagine characters arrive one at a time'.

By Sam K., Founder, InterviewChamp.AI · Last verified

Source citations

Public interview reports confirming this problem appears in JPMorgan loops.

  • Glassdoor (2026-Q1)JPMorgan SDE phone-screen reports list this as the standard string-frequency warm-up.
  • LeetCode (2026-Q1)Tagged JPMorgan on the LeetCode company tag page.
  • Reddit r/cscareerquestions (2025-10)JPMC SWE-I write-up: 'first unique char — then asked the streaming variant'.

Problem

Given a string s, find the first non-repeating character in it and return its index. If it does not exist, return -1.

Constraints

  • 1 <= s.length <= 10^5
  • s consists of only lowercase English letters.

Examples

Example 1

Input
s = "leetcode"
Output
0

Example 2

Input
s = "loveleetcode"
Output
2

Example 3

Input
s = "aabb"
Output
-1

Approaches

1. Two-pass with 26-slot frequency array (optimal)

First pass counts every character. Second pass returns the first index whose count is 1.

Time
O(n)
Space
O(1) (26 slots)
function firstUniqChar(s) {
  const count = new Array(26).fill(0);
  for (const ch of s) count[ch.charCodeAt(0) - 97]++;
  for (let i = 0; i < s.length; i++) {
    if (count[s.charCodeAt(i) - 97] === 1) return i;
  }
  return -1;
}

Tradeoff: Two passes but each is linear — total O(n) time and strictly O(1) space because the alphabet is fixed at 26. The cleanest answer for the stated constraint.

2. Single-pass with last-seen index tracking

Track each character's first index and a 'broken' flag. After one pass, scan the 26 first-indices for the smallest non-broken.

Time
O(n)
Space
O(1)
function firstUniqCharOnePass(s) {
  const first = new Array(26).fill(-1);
  const broken = new Array(26).fill(false);
  for (let i = 0; i < s.length; i++) {
    const c = s.charCodeAt(i) - 97;
    if (first[c] === -1) first[c] = i;
    else broken[c] = true;
  }
  let answer = Infinity;
  for (let c = 0; c < 26; c++) {
    if (!broken[c] && first[c] !== -1 && first[c] < answer) answer = first[c];
  }
  return answer === Infinity ? -1 : answer;
}

Tradeoff: Same asymptotic cost but only one pass over the input string (the final 26-element scan is O(1)). Preferred when the string is on disk or in a stream and you cannot conveniently scan it twice.

JPMorgan-specific tips

JPMorgan interviewers ask the streaming follow-up almost every time: 'now imagine characters arrive one at a time and you must answer the current first-unique on every tick'. The expected answer is a doubly-linked list of unique characters plus a Map<char, node> — O(1) per arrival. Mention this proactively after writing the two-pass solution; many candidates only solve the static version and miss the points.

Common mistakes

  • Using a hash map and reasoning over keys instead of the original string indices — gives the right character but the wrong index.
  • Forgetting that 'first' means first by position, not by alphabetical order.
  • Reading 'lowercase English' but writing the code assuming Unicode (needlessly large memory).
  • Returning the character instead of the index — the problem specifies index.

Follow-up questions

An interviewer at JPMorgan may pivot to one of these next:

  • Streaming variant: characters arrive one at a time, report current first-unique on every tick.
  • First unique number in a stream of integers.
  • Find all unique characters (not just the first).
  • What if you want the LAST unique character instead?

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

FAQ

Why is the alphabet-size constant for space?

The constraint pins s to lowercase English letters — exactly 26 possible characters. The count array has 26 slots regardless of input length, so the auxiliary space is O(1).

How do you handle the streaming variant?

Maintain a doubly linked list of characters that are currently still unique, plus a Map<char, node-or-null>. On arrival: if not in the map, append and store the node; if in the map with a node, remove the node and overwrite with null (marking as broken). The current first-unique is the head of the list, which is O(1) to read.

Free learning resources

Curated free links for this problem.

Companies that also ask First Unique Character in a String