Skip to main content

Command Palette

Search for a command to run...

JavaScript String Methods & Polyfills: Interview Questions You Must Know

Published
19 min read

Introduction

String manipulation is one of the most fundamental skills in JavaScript programming. Every day, developers work with strings: extracting data, transforming text, searching for patterns, and validating input. JavaScript provides many built-in string methods that make our lives easier, but understanding how these methods work under the hood is crucial for interviews, debugging, and writing efficient code. In this blog, we'll explore what string methods are, learn how to implement common ones as polyfills, solve real interview questions, and deepen your understanding of string processing in JavaScript.


1. What Are String Methods?

String methods are functions that operate on strings and perform specific tasks. JavaScript provides many built-in methods, but understanding their logic helps you write better code.

Built-in String Methods Overview:

const str = "Hello World";

// Length
console.log(str.length); // 11

// Case conversion
console.log(str.toUpperCase()); // "HELLO WORLD"
console.log(str.toLowerCase()); // "hello world"

// Accessing characters
console.log(str.charAt(0)); // "H"
console.log(str[0]); // "H"
console.log(str.charCodeAt(0)); // 72 (ASCII code)

// Searching
console.log(str.indexOf("o")); // 4
console.log(str.includes("World")); // true
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("World")); // true

// Extracting
console.log(str.substring(0, 5)); // "Hello"
console.log(str.slice(0, 5)); // "Hello"
console.log(str.substr(0, 5)); // "Hello" (deprecated)

// Splitting and joining
console.log(str.split(" ")); // ["Hello", "World"]
console.log("a-b-c".split("-")); // ["a", "b", "c"]

// Replacing
console.log(str.replace("World", "JavaScript")); // "Hello JavaScript"
console.log(str.replaceAll("l", "L")); // "HeLLo WorLd"

// Trimming
console.log("  Hello  ".trim()); // "Hello"
console.log("  Hello  ".trimStart()); // "Hello  "
console.log("  Hello  ".trimEnd()); // "  Hello"

// Repeating and padding
console.log("ab".repeat(3)); // "ababab"
console.log("5".padStart(3, "0")); // "005"
console.log("5".padEnd(3, "0")); // "500"

String Immutability:

Important: Strings in JavaScript are immutable. Methods don't change the original string, they return a new string.

const original = "Hello";

const upper = original.toUpperCase();
console.log(original); // "Hello" - Unchanged!
console.log(upper); // "HELLO" - New string

const replaced = original.replace("H", "J");
console.log(original); // "Hello" - Unchanged!
console.log(replaced); // "Jello" - New string

2. Why Developers Write Polyfills

A polyfill is code that provides functionality for browsers or environments that don't have it natively. String polyfills are important for several reasons.

Reason 1: Browser Compatibility

// Some older methods might not exist in older browsers
const str = "Hello World";

// Modern method (ES6+)
if (typeof str.includes !== "function") {
  // Polyfill: provide the functionality
  String.prototype.includes = function(searchStr) {
    return this.indexOf(searchStr) !== -1;
  };
}

console.log(str.includes("World")); // Works even in older browsers

Reason 2: Understanding Implementation

// By writing a polyfill, you understand how it works
// This is crucial for interviews!

// Example: Understanding indexOf
function myIndexOf(str, searchStr) {
  for (let i = 0; i < str.length; i++) {
    let found = true;
    for (let j = 0; j < searchStr.length; j++) {
      if (str[i + j] !== searchStr[j]) {
        found = false;
        break;
      }
    }
    if (found) {
      return i;
    }
  }
  return -1;
}

console.log(myIndexOf("Hello World", "World")); // 6

Reason 3: Interview Preparation

Interviewers often ask you to implement string methods. They want to see:

  • Your understanding of string algorithms

  • Your problem-solving approach

  • Your code quality and edge case handling

Reason 4: Using in Specific Environments

// Some methods might not be available in certain environments
// Example: Node.js might not have certain browser-specific methods

// Write a polyfill to ensure consistent behavior
if (!String.prototype.repeat) {
  String.prototype.repeat = function(count) {
    if (count < 0 || count === Infinity) {
      throw new RangeError("Invalid count value");
    }
    let str = "";
    for (let i = 0; i < count; i++) {
      str += this;
    }
    return str;
  };
}

Reason 5: Custom Extensions

// Sometimes you extend strings with custom methods
String.prototype.capitalize = function() {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

console.log("hello".capitalize()); // "Hello"

// Or reverse
String.prototype.reverse = function() {
  return this.split("").reverse().join("");
};

console.log("hello".reverse()); // "olleh"

3. Implementing Simple String Utilities

Let's implement common string methods to understand how they work.

Implementation 1: charAt()

// Get character at specific index
String.prototype.myCharAt = function(index) {
  if (index < 0 || index >= this.length) {
    return ""; // Return empty string if index out of bounds
  }
  return this[index];
};

console.log("Hello".myCharAt(0)); // "H"
console.log("Hello".myCharAt(1)); // "e"
console.log("Hello".myCharAt(10)); // ""

Implementation 2: indexOf()

// Find the index of a substring
String.prototype.myIndexOf = function(searchStr, fromIndex = 0) {
  if (!searchStr) {
    return 0; // Empty string found at position 0
  }
  
  for (let i = fromIndex; i <= this.length - searchStr.length; i++) {
    let found = true;
    
    // Check if substring matches at position i
    for (let j = 0; j < searchStr.length; j++) {
      if (this[i + j] !== searchStr[j]) {
        found = false;
        break;
      }
    }
    
    if (found) {
      return i;
    }
  }
  
  return -1; // Not found
};

console.log("Hello World".myIndexOf("World")); // 6
console.log("Hello World".myIndexOf("o")); // 4
console.log("Hello World".myIndexOf("xyz")); // -1
console.log("Hello World".myIndexOf("o", 5)); // 7 (finds second 'o')

Implementation 3: includes()

// Check if string contains substring
String.prototype.myIncludes = function(searchStr, position = 0) {
  return this.indexOf(searchStr, position) !== -1;
};

console.log("Hello World".myIncludes("World")); // true
console.log("Hello World".myIncludes("xyz")); // false

Implementation 4: startsWith()

// Check if string starts with substring
String.prototype.myStartsWith = function(searchStr, position = 0) {
  const substring = this.slice(position);
  return substring.indexOf(searchStr) === 0;
};

console.log("Hello World".myStartsWith("Hello")); // true
console.log("Hello World".myStartsWith("World")); // false
console.log("Hello World".myStartsWith("World", 6)); // true

Implementation 5: endsWith()

// Check if string ends with substring
String.prototype.myEndsWith = function(searchStr, length = this.length) {
  const substring = this.slice(0, length);
  return substring.slice(-searchStr.length) === searchStr;
};

console.log("Hello World".myEndsWith("World")); // true
console.log("Hello World".myEndsWith("Hello")); // false
console.log("Hello World".myEndsWith("World", 11)); // true

Implementation 6: slice()

// Extract a portion of string
String.prototype.mySlice = function(start = 0, end = this.length) {
  let actualStart = start < 0 ? Math.max(this.length + start, 0) : Math.min(start, this.length);
  let actualEnd = end < 0 ? Math.max(this.length + end, 0) : Math.min(end, this.length);
  
  let result = "";
  for (let i = actualStart; i < actualEnd; i++) {
    result += this[i];
  }
  return result;
};

console.log("Hello World".mySlice(0, 5)); // "Hello"
console.log("Hello World".mySlice(6)); // "World"
console.log("Hello World".mySlice(-5)); // "World"
console.log("Hello World".mySlice(-5, -1)); // "Worl"

Implementation 7: split()

// Split string by delimiter
String.prototype.mySplit = function(delimiter = undefined, limit = undefined) {
  const result = [];
  
  if (delimiter === undefined) {
    return [this.toString()];
  }
  
  if (delimiter === "") {
    // Split into individual characters
    for (let i = 0; i < this.length; i++) {
      if (limit && result.length >= limit) break;
      result.push(this[i]);
    }
    return result;
  }
  
  let currentStr = "";
  for (let i = 0; i < this.length; ) {
    let found = false;
    
    // Check if delimiter starts at position i
    if (i + delimiter.length <= this.length) {
      let match = true;
      for (let j = 0; j < delimiter.length; j++) {
        if (this[i + j] !== delimiter[j]) {
          match = false;
          break;
        }
      }
      
      if (match) {
        result.push(currentStr);
        if (limit && result.length >= limit) break;
        i += delimiter.length;
        currentStr = "";
        found = true;
      }
    }
    
    if (!found) {
      currentStr += this[i];
      i++;
    }
  }
  
  if (!found || currentStr !== "") {
    if (!limit || result.length < limit) {
      result.push(currentStr);
    }
  }
  
  return result;
};

console.log("a-b-c".mySplit("-")); // ["a", "b", "c"]
console.log("hello".mySplit("")); // ["h", "e", "l", "l", "o"]
console.log("a,b,c,d".mySplit(",", 2)); // ["a", "b"]

Implementation 8: toUpperCase() and toLowerCase()

// Convert to uppercase
String.prototype.myToUpperCase = function() {
  let result = "";
  for (let i = 0; i < this.length; i++) {
    const code = this.charCodeAt(i);
    // If lowercase letter (a-z: 97-122)
    if (code >= 97 && code <= 122) {
      result += String.fromCharCode(code - 32); // Convert to uppercase
    } else {
      result += this[i];
    }
  }
  return result;
};

// Convert to lowercase
String.prototype.myToLowerCase = function() {
  let result = "";
  for (let i = 0; i < this.length; i++) {
    const code = this.charCodeAt(i);
    // If uppercase letter (A-Z: 65-90)
    if (code >= 65 && code <= 90) {
      result += String.fromCharCode(code + 32); // Convert to lowercase
    } else {
      result[i];
    }
  }
  return result;
};

console.log("Hello".myToUpperCase()); // "HELLO"
console.log("Hello".myToLowerCase()); // "hello"

Implementation 9: replace()

// Replace first occurrence
String.prototype.myReplace = function(searchStr, replaceStr) {
  const index = this.indexOf(searchStr);
  
  if (index === -1) {
    return this.toString();
  }
  
  return this.slice(0, index) + replaceStr + this.slice(index + searchStr.length);
};

console.log("Hello World".myReplace("World", "JavaScript")); // "Hello JavaScript"
console.log("Hello Hello".myReplace("Hello", "Hi")); // "Hi Hello" (only first)

Implementation 10: trim()

// Remove whitespace from both ends
String.prototype.myTrim = function() {
  let start = 0;
  let end = this.length - 1;
  
  // Find first non-whitespace character
  while (start <= end && /\s/.test(this[start])) {
    start++;
  }
  
  // Find last non-whitespace character
  while (end >= start && /\s/.test(this[end])) {
    end--;
  }
  
  return this.slice(start, end + 1);
};

console.log("  Hello World  ".myTrim()); // "Hello World"
console.log("\n\tHello\n".myTrim()); // "Hello"

4. Common Interview String Problems

Let's solve classic interview questions about strings.

Problem 1: Reverse a String

// Problem: Reverse "Hello" → "olleH"

// Approach 1: Using split, reverse, join
function reverseString1(str) {
  return str.split("").reverse().join("");
}

// Approach 2: Using a loop
function reverseString2(str) {
  let result = "";
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  return result;
}

// Approach 3: Using reduce
function reverseString3(str) {
  return str.split("").reduce((rev, char) => char + rev, "");
}

// Approach 4: Using recursion
function reverseString4(str) {
  if (str.length <= 1) return str;
  return reverseString4(str.slice(1)) + str[0];
}

const test = "Hello";
console.log(reverseString1(test)); // "olleH"
console.log(reverseString2(test)); // "olleH"
console.log(reverseString3(test)); // "olleH"
console.log(reverseString4(test)); // "olleH"

Problem 2: Check if String is Palindrome

// Problem: Is "racecar" a palindrome?

function isPalindrome(str) {
  // Remove spaces and convert to lowercase
  const clean = str.replace(/\s/g, "").toLowerCase();
  
  // Compare with reverse
  const reversed = clean.split("").reverse().join("");
  
  return clean === reversed;
}

// Or: Two-pointer approach
function isPalindrome2(str) {
  const clean = str.replace(/\s/g, "").toLowerCase();
  
  let left = 0;
  let right = clean.length - 1;
  
  while (left < right) {
    if (clean[left] !== clean[right]) {
      return false;
    }
    left++;
    right--;
  }
  
  return true;
}

console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("hello")); // false
console.log(isPalindrome("A man a plan a canal Panama")); // true

Problem 3: Count Character Occurrences

// Problem: Count how many times each character appears in "hello"

function countCharacters(str) {
  const count = {};
  
  for (const char of str) {
    count[char] = (count[char] || 0) + 1;
  }
  
  return count;
}

// Or: Using Map
function countCharacters2(str) {
  const count = new Map();
  
  for (const char of str) {
    count.set(char, (count.get(char) || 0) + 1);
  }
  
  return count;
}

console.log(countCharacters("hello"));
// { h: 1, e: 1, l: 2, o: 1 }

console.log(countCharacters2("javascript"));
// Map(8) { 'j' => 1, 'a' => 2, 'v' => 1, 's' => 1, 'c' => 1, 'r' => 1, 'i' => 1, 't' => 1 }

Problem 4: Remove Duplicates from String

// Problem: "hello" → "helo"

// Approach 1: Using Set
function removeDuplicates1(str) {
  return [...new Set(str)].join("");
}

// Approach 2: Using object/map
function removeDuplicates2(str) {
  const seen = {};
  let result = "";
  
  for (const char of str) {
    if (!seen[char]) {
      result += char;
      seen[char] = true;
    }
  }
  
  return result;
}

// Approach 3: Using filter
function removeDuplicates3(str) {
  return str.split("").filter((char, index, arr) =>
    arr.indexOf(char) === index
  ).join("");
}

console.log(removeDuplicates1("hello")); // "helo"
console.log(removeDuplicates2("programming")); // "progamin"
console.log(removeDuplicates3("aabbcc")); // "abc"

Problem 5: Longest Substring Without Repeating Characters

// Problem: "abcabcbb" → "abc" (length 3)

function longestSubstring(str) {
  let maxLength = 0;
  let maxStart = 0;
  let currentStart = 0;
  const seen = {};
  
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    
    if (seen[char] !== undefined && seen[char] >= currentStart) {
      // Character seen before in current window
      currentStart = seen[char] + 1;
    }
    
    seen[char] = i;
    
    if (i - currentStart + 1 > maxLength) {
      maxLength = i - currentStart + 1;
      maxStart = currentStart;
    }
  }
  
  return str.slice(maxStart, maxStart + maxLength);
}

console.log(longestSubstring("abcabcbb")); // "abc"
console.log(longestSubstring("bbbbb")); // "b"
console.log(longestSubstring("pwwkew")); // "wke"
console.log(longestSubstring("au")); // "au"

Problem 6: Check if Two Strings are Anagrams

// Problem: Are "listen" and "silent" anagrams?

function isAnagram(str1, str2) {
  // Remove spaces and convert to lowercase
  const clean1 = str1.replace(/\s/g, "").toLowerCase();
  const clean2 = str2.replace(/\s/g, "").toLowerCase();
  
  // If lengths differ, not anagrams
  if (clean1.length !== clean2.length) {
    return false;
  }
  
  // Sort and compare
  const sorted1 = clean1.split("").sort().join("");
  const sorted2 = clean2.split("").sort().join("");
  
  return sorted1 === sorted2;
}

// Or: Using character count
function isAnagram2(str1, str2) {
  const clean1 = str1.replace(/\s/g, "").toLowerCase();
  const clean2 = str2.replace(/\s/g, "").toLowerCase();
  
  if (clean1.length !== clean2.length) return false;
  
  const count = {};
  
  for (const char of clean1) {
    count[char] = (count[char] || 0) + 1;
  }
  
  for (const char of clean2) {
    if (!count[char]) {
      return false;
    }
    count[char]--;
  }
  
  return true;
}

console.log(isAnagram("listen", "silent")); // true
console.log(isAnagram("hello", "world")); // false
console.log(isAnagram("Astronomer", "Moon starer")); // true

Problem 7: Capitalize First Letter of Each Word

// Problem: "hello world" → "Hello World"

function capitalizeWords1(str) {
  return str
    .split(" ")
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(" ");
}

// Or: Using regex
function capitalizeWords2(str) {
  return str.replace(/\b\w/g, char => char.toUpperCase());
}

console.log(capitalizeWords1("hello world javascript")); // "Hello World Javascript"
console.log(capitalizeWords2("the quick brown fox")); // "The Quick Brown Fox"

Problem 8: String Compression

// Problem: "aaabbc" → "a3b2c1"

function compressString(str) {
  if (!str || str.length <= 1) return str;
  
  let compressed = "";
  let count = 1;
  
  for (let i = 0; i < str.length; i++) {
    // If next character is different or last character
    if (i + 1 >= str.length || str[i] !== str[i + 1]) {
      compressed += str[i] + count;
      count = 1;
    } else {
      count++;
    }
  }
  
  // Return compressed only if it's shorter
  return compressed.length < str.length ? compressed : str;
}

console.log(compressString("abcccccaab")); // "a1b1c5a2b1"
console.log(compressString("abcdef")); // "abcdef" (not shorter)
console.log(compressString("aabbcc")); // "aabbcc" (not shorter)

Problem 9: First Non-Repeating Character

// Problem: "leetcode" → "l"

function firstNonRepeating(str) {
  const count = {};
  
  // Count occurrences
  for (const char of str) {
    count[char] = (count[char] || 0) + 1;
  }
  
  // Find first non-repeating
  for (const char of str) {
    if (count[char] === 1) {
      return char;
    }
  }
  
  return null; // All characters repeat
}

console.log(firstNonRepeating("leetcode")); // "l"
console.log(firstNonRepeating("loveleetcode")); // "v"
console.log(firstNonRepeating("aabb")); // null

Problem 10: Valid Parentheses/Brackets

// Problem: Are "{[()]}" parentheses valid?

function isValidParentheses(str) {
  const stack = [];
  const pairs = {
    ")": "(",
    "}": "{",
    "]": "["
  };
  
  for (const char of str) {
    if (char === "(" || char === "{" || char === "[") {
      stack.push(char);
    } else if (char === ")" || char === "}" || char === "]") {
      if (stack.length === 0 || stack.pop() !== pairs[char]) {
        return false;
      }
    }
  }
  
  return stack.length === 0;
}

console.log(isValidParentheses("{[()]}")); // true
console.log(isValidParentheses("{[(])}")); // false
console.log(isValidParentheses("()")); // true
console.log(isValidParentheses("({[}])")); // false

5. Importance of Understanding Built-in Behavior

Understanding how built-in methods work is crucial for several reasons.

Reason 1: Edge Cases

// Understanding edge cases prevents bugs

// indexOf with empty string
console.log("hello".indexOf("")); // 0
console.log("hello".indexOf("", 3)); // 3

// slice with negative indices
console.log("hello".slice(-3)); // "llo"
console.log("hello".slice(-3, -1)); // "ll"

// split with empty delimiter
console.log("hello".split("")); // ["h", "e", "l", "l", "o"]

// includes is case-sensitive
console.log("Hello".includes("hello")); // false

Reason 2: Performance

// Knowing which methods are efficient

// Bad: Creating many intermediate strings
let result = "";
for (let i = 0; i < 1000; i++) {
  result += "x"; // Creates new string each time - O(n²)
}

// Better: Use join
const arr = [];
for (let i = 0; i < 1000; i++) {
  arr.push("x");
}
const result = arr.join(""); // O(n)

// Or: Use repeat
const result = "x".repeat(1000); // Most efficient

Reason 3: Regex Integration

// Many methods work with regex

// replace with regex
const str = "apple apple apple";
console.log(str.replace("apple", "orange")); // "orange apple apple"
console.log(str.replace(/apple/g, "orange")); // "orange orange orange"

// split with regex
console.log("a1b2c3".split(/\d/)); // ["a", "b", "c", ""]

// match with regex
console.log("hello123world456".match(/\d+/g)); // ["123", "456"]

Reason 4: Unicode Handling

// Some methods handle Unicode differently

const emoji = "😀😀😀";

console.log(emoji.length); // 6 (each emoji is 2 characters)
console.log([...emoji].length); // 3 (correct count)

// charAt vs square bracket vs [...str]
console.log(emoji.charAt(0)); // "?" (incorrect for emoji)
console.log(emoji[0]); // "?" (incorrect for emoji)
console.log([...emoji][0]); // "😀" (correct)

Reason 5: Immutability Awareness

// Strings are immutable - methods return new strings

let str = "hello";
str.toUpperCase(); // Doesn't modify str
console.log(str); // "hello" - unchanged

// Must assign to variable
str = str.toUpperCase();
console.log(str); // "HELLO" - now changed

// This is different from arrays
const arr = [1, 2, 3];
arr.reverse(); // Modifies arr in place
console.log(arr); // [3, 2, 1] - changed

6. Real-World String Processing Examples

Example 1: Email Validation

function validateEmail(email) {
  // Basic email validation
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

console.log(validateEmail("user@example.com")); // true
console.log(validateEmail("invalid.email")); // false
console.log(validateEmail("user@domain.co.uk")); // true

Example 2: URL Parsing

function parseUrl(url) {
  const parts = url.split("://");
  const protocol = parts[0];
  
  const remaining = parts[1];
  const pathIndex = remaining.indexOf("/");
  const domain = remaining.slice(0, pathIndex);
  const path = remaining.slice(pathIndex);
  
  return { protocol, domain, path };
}

const result = parseUrl("https://example.com/path/to/page");
console.log(result);
// { protocol: 'https', domain: 'example.com', path: '/path/to/page' }

Example 3: String Templating

function formatString(template, data) {
  return template.replace(/\{(\w+)\}/g, (match, key) => {
    return data[key] || match;
  });
}

const template = "Hello {name}, you are {age} years old";
const data = { name: "Ashish", age: 21 };

console.log(formatString(template, data));
// "Hello Ashish, you are 21 years old"

Example 4: CSV Parsing

function parseCSV(csvString) {
  const rows = csvString.split("\n");
  const headers = rows[0].split(",").map(h => h.trim());
  
  const data = rows.slice(1).map(row => {
    const values = row.split(",").map(v => v.trim());
    const obj = {};
    headers.forEach((header, index) => {
      obj[header] = values[index];
    });
    return obj;
  });
  
  return data;
}

const csv = `Name,Age,City
Raj,25,Jaipur
Priya,23,Delhi
Anil,26,Mumbai`;

console.log(parseCSV(csv));
// [
//   { Name: "Raj", Age: "25", City: "Jaipur" },
//   { Name: "Priya", Age: "23", City: "Delhi" },
//   { Name: "Anil", Age: "26", City: "Mumbai" }
// ]

7. Interview Tips for String Problems

Tip 1: Clarify the Problem

// Before coding, ask questions:
// - Should uppercase and lowercase be treated the same?
// - Should spaces be ignored?
// - What about special characters?
// - What should be returned if not found?

function search(str, pattern, options = {}) {
  const { caseSensitive = true, ignoreSpaces = false } = options;
  
  let searchStr = str;
  let searchPattern = pattern;
  
  if (!caseSensitive) {
    searchStr = searchStr.toLowerCase();
    searchPattern = searchPattern.toLowerCase();
  }
  
  if (ignoreSpaces) {
    searchStr = searchStr.replace(/\s/g, "");
    searchPattern = searchPattern.replace(/\s/g, "");
  }
  
  return searchStr.includes(searchPattern);
}

Tip 2: Test with Edge Cases

function reverseString(str) {
  return str.split("").reverse().join("");
}

// Test cases
console.log(reverseString("")); // ""
console.log(reverseString("a")); // "a"
console.log(reverseString("ab")); // "ba"
console.log(reverseString("hello world")); // "dlrow olleh"
console.log(reverseString("123")); // "321"

Tip 3: Discuss Time and Space Complexity

// O(n) time, O(n) space - creates new array and string
function method1(str) {
  return str.split("").reverse().join("");
}

// O(n) time, O(n) space - builds new string character by character
function method2(str) {
  let result = "";
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  return result;
}

// Both are O(n) time and space, but method1 is cleaner

Tip 4: Show Your Thinking

// Good: Explain your approach
function isPalindrome(str) {
  // Step 1: Clean the string (remove spaces, convert to lowercase)
  const clean = str.replace(/\s/g, "").toLowerCase();
  
  // Step 2: Compare with reverse
  const reversed = clean.split("").reverse().join("");
  
  // Step 3: Return result
  return clean === reversed;
}

// Walk through with example:
// Input: "A man a plan a canal Panama"
// Step 1: "amanaplanacanalpanama"
// Step 2: "amanaplanacanalpanama"
// Step 3: true

Tip 5: Optimize If Needed

// Initial solution
function countVowels(str) {
  let count = 0;
  for (const char of str) {
    if ("aeiouAEIOU".includes(char)) {
      count++;
    }
  }
  return count;
}

// Optimized with Set for faster lookup
function countVowels(str) {
  const vowels = new Set("aeiouAEIOU".split(""));
  let count = 0;
  for (const char of str) {
    if (vowels.has(char)) {
      count++;
    }
  }
  return count;
}

// Or use regex
function countVowels(str) {
  return (str.match(/[aeiou]/gi) || []).length;
}

8. Common Mistakes to Avoid

Mistake 1: Forgetting Strings Are Immutable

// Wrong: Expecting string to change
let str = "hello";
str.toUpperCase(); // Doesn't modify str
console.log(str); // "hello" - still lowercase!

// Right: Assign the result
str = str.toUpperCase();
console.log(str); // "HELLO"

Mistake 2: Off-by-One Errors with Indices

const str = "hello";

// Wrong: Misunderstanding slice behavior
console.log(str.slice(0, 2)); // "he" - NOT "hel"!

// Wrong: Using substring when you meant slice
console.log(str.substring(1, 1)); // "" - both params inclusive in some ways

// Right: Remember slice is exclusive on the right
console.log(str.slice(0, 3)); // "hel"
console.log(str.slice(1, 4)); // "ell"

Mistake 3: Case Sensitivity

// Wrong: Assuming case-insensitive
console.log("Hello".includes("hello")); // false!

// Right: Convert to same case
console.log("Hello".toLowerCase().includes("hello")); // true

Mistake 4: Not Handling Empty Strings

// Wrong: Not checking for empty
function getFirstChar(str) {
  return str[0]; // Returns undefined for empty string
}

// Right: Handle edge case
function getFirstChar(str) {
  return str.length > 0 ? str[0] : "";
}

Mistake 5: Regex Without Global Flag

// Wrong: Replaces only first match
const result = "hello hello".replace(/hello/, "hi");
console.log(result); // "hi hello"

// Right: Use global flag for all matches
const result = "hello hello".replace(/hello/g, "hi");
console.log(result); // "hi hi"

Conclusion

String manipulation is a fundamental JavaScript skill. Here's what we've learned:

Key Takeaways:

  1. Understand built-in methods - Know how charAt(), indexOf(), slice(), split() work

  2. Write polyfills - Implement methods to understand their logic

  3. Solve interview problems - Practice reversing, palindromes, anagrams, etc.

  4. Edge cases matter - Handle empty strings, special characters, Unicode

  5. Performance counts - Know which methods are efficient

  6. Immutability - Remember strings don't change, methods return new strings

Interview Preparation:

  • Practice common string problems daily

  • Explain your approach before coding

  • Test with edge cases

  • Discuss time and space complexity

  • Show your thinking process

Real-World Applications:

  • Email and URL validation

  • Text processing and formatting

  • Data parsing (CSV, JSON)

  • Search and replace operations

  • String compression and encryption

Master string methods and polyfill implementation, and you'll be well-prepared for technical interviews and real-world JavaScript development. String manipulation is everywhere - from form validation to data processing - so investing time to understand these concepts deeply will pay dividends throughout your career.