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 Alex Chen, 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.
Practice these live with InterviewChamp.AI
Drill First Unique Character in a String and other JPMorgan interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →