🎯 Top 25 JavaScript String Interview Questions

Parent: Strings JS Index


Quick Reference

#ProblemDifficultyPatternTime
1Reverse StringETwo PointersO(n)
2Valid PalindromeETwo PointersO(n)
3Valid AnagramEFrequency MapO(n)
4First Unique CharacterEFrequency MapO(n)
5Reverse WordsMSplit + ReverseO(n)
6String CompressionMTwo PointersO(n)
7Valid ParenthesesEStackO(n)
8Longest Substring No RepeatMSliding WindowO(n)
9Longest Palindromic SubstringMExpand CenterO(nΒ²)
10Group AnagramsMHash MapO(nΒ·k log k)
11Implement strStr (KMP)EKMPO(n+m)
12Count & SayMSimulationO(n·2ⁿ)
13Minimum Window SubstringHSliding WindowO(n+m)
14Longest Common PrefixEHorizontal ScanO(nΒ·m)
15Roman to IntegerEHash MapO(n)
16Integer to RomanMGreedyO(1)
17Decode StringMStackO(n)
18Word BreakMRecursion + MemoO(nΒ²)
19Palindrome PartitioningMBacktrackingO(n·2ⁿ)
20Zigzag ConversionMSimulationO(n)
21Longest Repeating Char ReplaceMSliding WindowO(n)
22Minimum Deletions for Unique FreqMGreedyO(n)
23Isomorphic StringsEHash MapO(n)
24Largest NumberMCustom SortO(n log n)
25Find All AnagramsMSliding WindowO(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 w and read pointer i. 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+b vs b+a as 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

TechniqueProblems
Two PointersPalindrome, Reverse String, Reverse Vowels
Sliding WindowLongest No-Repeat, Min Window, Longest Repeat Replace, Find Anagrams
Frequency MapAnagram, Group Anagrams, First Unique Char
StackValid Parentheses, Decode String
Expand CenterLongest Palindromic Substring
SimulationCount&Say, Zigzag
GreedyInteger to Roman, Largest Number, Min Deletions
Recursion+MemoWord Break, Palindrome Partition