π― Top 25 JavaScript String Interview Questions
Parent: Strings JS Index
Quick Reference
| # | Problem | Difficulty | Pattern | Time |
|---|---|---|---|---|
| 1 | Reverse String | E | Two Pointers | O(n) |
| 2 | Valid Palindrome | E | Two Pointers | O(n) |
| 3 | Valid Anagram | E | Frequency Map | O(n) |
| 4 | First Unique Character | E | Frequency Map | O(n) |
| 5 | Reverse Words | M | Split + Reverse | O(n) |
| 6 | String Compression | M | Two Pointers | O(n) |
| 7 | Valid Parentheses | E | Stack | O(n) |
| 8 | Longest Substring No Repeat | M | Sliding Window | O(n) |
| 9 | Longest Palindromic Substring | M | Expand Center | O(nΒ²) |
| 10 | Group Anagrams | M | Hash Map | O(nΒ·k log k) |
| 11 | Implement strStr (KMP) | E | KMP | O(n+m) |
| 12 | Count & Say | M | Simulation | O(nΒ·2βΏ) |
| 13 | Minimum Window Substring | H | Sliding Window | O(n+m) |
| 14 | Longest Common Prefix | E | Horizontal Scan | O(nΒ·m) |
| 15 | Roman to Integer | E | Hash Map | O(n) |
| 16 | Integer to Roman | M | Greedy | O(1) |
| 17 | Decode String | M | Stack | O(n) |
| 18 | Word Break | M | Recursion + Memo | O(nΒ²) |
| 19 | Palindrome Partitioning | M | Backtracking | O(nΒ·2βΏ) |
| 20 | Zigzag Conversion | M | Simulation | O(n) |
| 21 | Longest Repeating Char Replace | M | Sliding Window | O(n) |
| 22 | Minimum Deletions for Unique Freq | M | Greedy | O(n) |
| 23 | Isomorphic Strings | E | Hash Map | O(n) |
| 24 | Largest Number | M | Custom Sort | O(n log n) |
| 25 | Find All Anagrams | M | Sliding Window | O(n) |
Q1. Reverse String [E][LC-344]
π§ How to Approach This
Step 1: Two pointers. Swap first and last, move inward. In-place on char array.
function reverseString(s) {
let lo = 0, hi = s.length - 1;
while (lo < hi) {
[s[lo], s[hi]] = [s[hi], s[lo]];
lo++; hi--;
}
}
const chars = ['h','e','l','l','o'];
reverseString(chars);
console.log(chars.join('')); // "olleh"
Time: O(n) | Space: O(1)
Q2. Valid Palindrome [E][LC-125]
π§ How to Approach This
Step 1: Skip non-alphanumeric with regex. Compare ends moving inward. Case-insensitive.
function isPalindrome(s) {
let lo = 0, hi = s.length - 1;
while (lo < hi) {
while (lo < hi && !/[a-z0-9]/i.test(s[lo])) lo++;
while (lo < hi && !/[a-z0-9]/i.test(s[hi])) hi--;
if (s[lo].toLowerCase() !== s[hi].toLowerCase()) return false;
lo++; hi--;
}
return true;
}
console.log(isPalindrome("A man, a plan, a canal: Panama")); // true
console.log(isPalindrome("race a car")); // false
Time: O(n) | Space: O(1)
Q3. Valid Anagram [E][LC-242]
π§ How to Approach This
Step 1: Same length? Count freq with array[26]. Increment for s, decrement for t. Any nonzero β false.
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const freq = new Array(26).fill(0);
for (let i = 0; i < s.length; i++) {
freq[s.charCodeAt(i) - 97]++;
freq[t.charCodeAt(i) - 97]--;
}
return freq.every(v => v === 0);
}
console.log(isAnagram("anagram","nagaram")); // true
console.log(isAnagram("rat","car")); // false
Time: O(n) | Space: O(1)
Q4. First Unique Character [E][LC-387]
π§ How to Approach This
Step 1: Count all char frequencies. Scan left-to-right for first with count == 1.
function firstUniqChar(s) {
const freq = new Map();
for (const c of s) freq.set(c, (freq.get(c) || 0) + 1);
for (let i = 0; i < s.length; i++) {
if (freq.get(s[i]) === 1) return i;
}
return -1;
}
console.log(firstUniqChar("leetcode")); // 0
console.log(firstUniqChar("loveleetcode")); // 2
Time: O(n) | Space: O(1)
Q5. Reverse Words in String [M][LC-151]
π§ How to Approach This
Step 1: Trim β split by whitespace (handles multiple spaces) β reverse β join with single space.
function reverseWords(s) {
return s.trim().split(/\s+/).reverse().join(' ');
}
console.log(reverseWords(" the sky is blue ")); // "blue is sky the"
console.log(reverseWords("a good example")); // "example good a"
Time: O(n) | Space: O(n)
Q6. String Compression [M][LC-443]
π§ How to Approach This
Step 1: Two pointers β write pointer
wand read pointeri. Count consecutive equal chars. Write char + count (if > 1).
function compress(chars) {
let w = 0, i = 0;
while (i < chars.length) {
const c = chars[i]; let count = 0;
while (i < chars.length && chars[i] === c) { i++; count++; }
chars[w++] = c;
if (count > 1) for (const d of String(count)) chars[w++] = d;
}
return w;
}
const c = ['a','a','b','b','c','c','c'];
console.log(compress(c)); // 6 β ['a','2','b','2','c','3']
Time: O(n) | Space: O(1)
Q7. Valid Parentheses [E][LC-20]
π§ How to Approach This
Step 1: Stack. Push open brackets. On close: check if stack top matches. Empty at end β valid.
function isValid(s) {
const stack = [];
const map = {')':'(', ']':'[', '}':'{'};
for (const c of s) {
if ('([{'.includes(c)) { stack.push(c); }
else if (map[c]) {
if (!stack.length || stack.pop() !== map[c]) return false;
}
}
return stack.length === 0;
}
console.log(isValid("()[]{}")); // true
console.log(isValid("([)]")); // false
Time: O(n) | Space: O(n)
Q8. Longest Substring Without Repeating Characters [M][LC-3]
π§ How to Approach This
Step 1 β Sliding Window: Map char β last index. On duplicate: move left past it. Track max.
function lengthOfLongestSubstring(s) {
const map = new Map();
let max = 0, left = 0;
for (let r = 0; r < s.length; r++) {
if (map.has(s[r]) && map.get(s[r]) >= left) left = map.get(s[r]) + 1;
map.set(s[r], r);
max = Math.max(max, r - left + 1);
}
return max;
}
console.log(lengthOfLongestSubstring("abcabcbb")); // 3
console.log(lengthOfLongestSubstring("pwwkew")); // 3
Time: O(n) | Space: O(k) charset
Q9. Longest Palindromic Substring [M][LC-5]
π§ How to Approach This
Step 1 β Expand Center: For each index, expand outward (odd + even centers). Track longest.
function longestPalindrome(s) {
let best = '';
const expand = (lo, hi) => {
while (lo >= 0 && hi < s.length && s[lo] === s[hi]) { lo--; hi++; }
return s.slice(lo + 1, hi);
};
for (let i = 0; i < s.length; i++) {
for (const p of [expand(i,i), expand(i,i+1)])
if (p.length > best.length) best = p;
}
return best;
}
console.log(longestPalindrome("babad")); // "bab"
console.log(longestPalindrome("cbbd")); // "bb"
Time: O(nΒ²) | Space: O(1)
Q10. Group Anagrams [M][LC-49]
π§ How to Approach This
Step 1 β Key insight: Anagrams share the same sorted form. Group by sorted chars as key.
function groupAnagrams(strs) {
const map = new Map();
for (const s of strs) {
const key = s.split('').sort().join('');
if (!map.has(key)) map.set(key, []);
map.get(key).push(s);
}
return [...map.values()];
}
console.log(groupAnagrams(["eat","tea","tan","ate","nat","bat"]));
// [["eat","tea","ate"],["tan","nat"],["bat"]]
Time: O(n Γ k log k) | Space: O(nΓk)
Q11. Implement strStr / Needle in Haystack [E][LC-28]
π§ How to Approach This
Step 1 β KMP: Build LPS table, then O(n+m) search. Use for interviews β shows depth.
function strStr(haystack, needle) {
if (!needle) return 0;
const n = haystack.length, m = needle.length;
// Build LPS
const lps = new Array(m).fill(0);
for (let len = 0, i = 1; i < m; ) {
if (needle[i] === needle[len]) { lps[i++] = ++len; }
else if (len > 0) { len = lps[len - 1]; }
else { lps[i++] = 0; }
}
// KMP search
for (let i = 0, j = 0; i < n; ) {
if (haystack[i] === needle[j]) { i++; j++; }
if (j === m) return i - j;
else if (i < n && haystack[i] !== needle[j])
j > 0 ? (j = lps[j - 1]) : i++;
}
return -1;
}
console.log(strStr("hello", "ll")); // 2
console.log(strStr("aaaaa", "bba")); // -1
Time: O(n+m) | Space: O(m)
Q12. Count and Say [M][LC-38]
π§ How to Approach This
Step 1 β Simulate: Count consecutive digits. Build next string as "countDigit".
function countAndSay(n) {
let s = '1';
for (let i = 1; i < n; i++) {
let next = ''; let j = 0;
while (j < s.length) {
const c = s[j]; let cnt = 0;
while (j < s.length && s[j] === c) { j++; cnt++; }
next += cnt + c;
}
s = next;
}
return s;
}
console.log(countAndSay(1)); // "1"
console.log(countAndSay(4)); // "1211"
console.log(countAndSay(5)); // "111221"
Time: O(n Γ 2βΏ) | Space: O(2βΏ)
Q13. Minimum Window Substring [H][LC-76]
π§ How to Approach This
Step 1 β Sliding Window + two Maps: Track how many chars from t are satisfied. Shrink left when all satisfied.
function minWindow(s, t) {
if (t.length > s.length) return '';
const need = new Map();
for (const c of t) need.set(c, (need.get(c) || 0) + 1);
const have = new Map();
let formed = 0, required = need.size, best = '', lo = 0;
for (let r = 0; r < s.length; r++) {
const c = s[r];
have.set(c, (have.get(c) || 0) + 1);
if (need.has(c) && have.get(c) === need.get(c)) formed++;
while (formed === required) {
const win = s.slice(lo, r + 1);
if (!best || win.length < best.length) best = win;
const lc = s[lo++];
have.set(lc, have.get(lc) - 1);
if (need.has(lc) && have.get(lc) < need.get(lc)) formed--;
}
}
return best;
}
console.log(minWindow("ADOBECODEBANC", "ABC")); // "BANC"
Time: O(n+m) | Space: O(n+m)
Q14. Longest Common Prefix [E][LC-14]
π§ How to Approach This
Step 1 β Horizontal Scan: Use first string as prefix. Shorten until all start with it.
function longestCommonPrefix(strs) {
if (!strs.length) return '';
let prefix = strs[0];
for (const s of strs) {
while (!s.startsWith(prefix)) prefix = prefix.slice(0, -1);
if (!prefix) return '';
}
return prefix;
}
console.log(longestCommonPrefix(["flower","flow","flight"])); // "fl"
console.log(longestCommonPrefix(["dog","racecar","car"])); // ""
Time: O(nΓm) | Space: O(1)
Q15. Roman to Integer [E][LC-13]
π§ How to Approach This
Step 1: Map symbols to values. If current value < next β subtract. Else add.
function romanToInt(s) {
const map = {I:1,V:5,X:10,L:50,C:100,D:500,M:1000};
let res = 0;
for (let i = 0; i < s.length; i++) {
const curr = map[s[i]], next = map[s[i+1]] || 0;
res += curr < next ? -curr : curr;
}
return res;
}
console.log(romanToInt("III")); // 3
console.log(romanToInt("MCMXCIV")); // 1994
Time: O(n) | Space: O(1)
Q16. Integer to Roman [M][LC-12]
π§ How to Approach This
Step 1 β Greedy: Values+symbols from largest to smallest. Subtract greedily.
function intToRoman(num) {
const vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
const syms = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"];
let res = '';
for (let i = 0; i < vals.length; i++) {
while (num >= vals[i]) { res += syms[i]; num -= vals[i]; }
}
return res;
}
console.log(intToRoman(1994)); // "MCMXCIV"
console.log(intToRoman(58)); // "LVIII"
Time: O(1) bounded | Space: O(1)
Q17. Decode String [M][LC-394]
π§ How to Approach This
Step 1 β Two stacks: Count stack and string stack. On '[': push. On ']': pop and repeat curr string.
function decodeString(s) {
const cntStack = [], strStack = [];
let curr = '', k = 0;
for (const c of s) {
if (c >= '0' && c <= '9') { k = k * 10 + +c; }
else if (c === '[') { cntStack.push(k); strStack.push(curr); curr = ''; k = 0; }
else if (c === ']') { curr = strStack.pop() + curr.repeat(cntStack.pop()); }
else { curr += c; }
}
return curr;
}
console.log(decodeString("3[a]2[bc]")); // "aaabcbc"
console.log(decodeString("3[a2[c]]")); // "accaccacc"
Time: O(output size) | Space: O(n)
Q18. Word Break [M][LC-139]
π§ How to Approach This
Step 1 β Recursion + Memo: From index i, try every prefix. If in Set, recurse. Memoize index β result.
function wordBreak(s, wordDict, start = 0, memo = new Map()) {
if (start === s.length) return true;
if (memo.has(start)) return memo.get(start);
const words = new Set(wordDict);
for (let end = start + 1; end <= s.length; end++) {
if (words.has(s.slice(start, end)) && wordBreak(s, wordDict, end, memo)) {
memo.set(start, true);
return true;
}
}
memo.set(start, false);
return false;
}
console.log(wordBreak("leetcode", ["leet","code"])); // true
console.log(wordBreak("applepenapple", ["apple","pen"])); // true
console.log(wordBreak("catsandog", ["cats","dog","sand"])); // false
Time: O(nΒ²) | Space: O(n)
Q19. Palindrome Partitioning [M][LC-131]
π§ How to Approach This
Step 1 β Backtracking: Try each prefix. If palindrome β recurse on rest. Collect at base case.
function partition(s) {
const result = [], curr = [];
const isPalin = str => str === str.split('').reverse().join('');
function bt(start) {
if (start === s.length) { result.push([...curr]); return; }
for (let end = start + 1; end <= s.length; end++) {
const sub = s.slice(start, end);
if (isPalin(sub)) {
curr.push(sub); bt(end); curr.pop();
}
}
}
bt(0);
return result;
}
console.log(partition("aab")); // [["a","a","b"],["aa","b"]]
Time: O(n Γ 2βΏ) | Space: O(n)
Q20. Zigzag Conversion [M][LC-6]
π§ How to Approach This
Step 1 β Simulate: Rows array. Direction toggles at first/last row. Concat all rows.
function convert(s, numRows) {
if (numRows === 1) return s;
const rows = Array.from({length: numRows}, () => '');
let row = 0, down = true;
for (const c of s) {
rows[row] += c;
if (row === 0) down = true;
if (row === numRows - 1) down = false;
row += down ? 1 : -1;
}
return rows.join('');
}
console.log(convert("PAYPALISHIRING", 3)); // "PAHNAPLSIIGYIR"
console.log(convert("PAYPALISHIRING", 4)); // "PINALSIGYAHRPI"
Time: O(n) | Space: O(n)
Q21. Longest Repeating Character Replacement [M][LC-424]
π§ How to Approach This
Step 1 β Sliding Window: Track max freq in window. If
windowSize - maxFreq > kβ shrink left.
function characterReplacement(s, k) {
const freq = new Array(26).fill(0);
let maxFreq = 0, maxLen = 0, lo = 0;
for (let r = 0; r < s.length; r++) {
maxFreq = Math.max(maxFreq, ++freq[s.charCodeAt(r) - 65]);
while ((r - lo + 1) - maxFreq > k)
freq[s.charCodeAt(lo++) - 65]--;
maxLen = Math.max(maxLen, r - lo + 1);
}
return maxLen;
}
console.log(characterReplacement("AABABBA", 1)); // 4
console.log(characterReplacement("ABAB", 2)); // 4
Time: O(n) | Space: O(1)
Q22. Minimum Deletions to Make Char Frequencies Unique [M][LC-1647]
π§ How to Approach This
Step 1 β Greedy: Count frequencies. Sort descending. For each freq, keep reducing until unique or zero.
function minDeletions(s) {
const freq = new Array(26).fill(0);
for (const c of s) freq[c.charCodeAt(0) - 97]++;
freq.sort((a,b) => b - a);
const used = new Set();
let dels = 0;
for (let f of freq) {
while (f > 0 && used.has(f)) { f--; dels++; }
if (f > 0) used.add(f);
}
return dels;
}
console.log(minDeletions("aab")); // 0
console.log(minDeletions("aaabbbcc")); // 2
Time: O(n + 26 log 26) | Space: O(1)
Q23. Isomorphic Strings [E][LC-205]
π§ How to Approach This
Step 1: Map sβt chars. Map tβs chars. Check both mappings consistent and bijective.
function isIsomorphic(s, t) {
const st = new Map(), ts = new Map();
for (let i = 0; i < s.length; i++) {
const a = s[i], b = t[i];
if ((st.has(a) && st.get(a) !== b) ||
(ts.has(b) && ts.get(b) !== a)) return false;
st.set(a, b); ts.set(b, a);
}
return true;
}
console.log(isIsomorphic("egg","add")); // true
console.log(isIsomorphic("foo","bar")); // false
console.log(isIsomorphic("paper","title")); // true
Time: O(n) | Space: O(1)
Q24. Largest Number [M][LC-179]
π§ How to Approach This
Step 1 β Custom Sort: Compare
a+bvsb+aas strings. Whichever is larger goes first.
function largestNumber(nums) {
const sorted = nums.map(String).sort((a,b) => (b+a).localeCompare(a+b));
const res = sorted.join('');
return res[0] === '0' ? '0' : res;
}
console.log(largestNumber([3,30,34,5,9])); // "9534330"
console.log(largestNumber([10,2])); // "210"
Time: O(n log n) | Space: O(n)
Q25. Find All Anagrams in String [M][LC-438]
π§ How to Approach This
Step 1 β Fixed Sliding Window: Window = len(p). Slide right by 1, add new char, remove old char. Compare freq arrays.
function findAnagrams(s, p) {
const result = [];
const m = s.length, k = p.length;
if (m < k) return result;
const pFreq = new Array(26).fill(0);
const wFreq = new Array(26).fill(0);
for (const c of p) pFreq[c.charCodeAt(0) - 97]++;
for (let i = 0; i < k; i++) wFreq[s.charCodeAt(i) - 97]++;
if (pFreq.join() === wFreq.join()) result.push(0);
for (let r = k; r < m; r++) {
wFreq[s.charCodeAt(r) - 97]++;
wFreq[s.charCodeAt(r - k) - 97]--;
if (pFreq.join() === wFreq.join()) result.push(r - k + 1);
}
return result;
}
console.log(findAnagrams("cbaebabacd","abc")); // [0, 6]
console.log(findAnagrams("abab","ab")); // [0, 1, 2]
Time: O(n) | Space: O(1)
Pattern Summary
| Technique | Problems |
|---|---|
| Two Pointers | Palindrome, Reverse String, Reverse Vowels |
| Sliding Window | Longest No-Repeat, Min Window, Longest Repeat Replace, Find Anagrams |
| Frequency Map | Anagram, Group Anagrams, First Unique Char |
| Stack | Valid Parentheses, Decode String |
| Expand Center | Longest Palindromic Substring |
| Simulation | Count&Say, Zigzag |
| Greedy | Integer to Roman, Largest Number, Min Deletions |
| Recursion+Memo | Word Break, Palindrome Partition |