387. First Unique Character in a String
easyAsked at JPMorganReturn 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^5s consists of only lowercase English letters.
Examples
Example 1
s = "leetcode"0Example 2
s = "loveleetcode"2Example 3
s = "aabb"-1Approaches
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.
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.