Skip to main content

Command Palette

Search for a command to run...

String Polyfills and Common Interview Methods in JavaScript

Updated
19 min read
String Polyfills and Common Interview Methods in JavaScript
H
CS undergrad | Tech enthusiast | Focusing on Web Dev • DSA • ML | Building skills for real-world impact

Every JavaScript developer uses string methods. But do you know how they work? Building your own string utilities is more than an interview trick—it's how you truly understand JavaScript. And understanding them deeply makes you a better developer.

This is about how string methods work, why developers write polyfills, and how to ace those string manipulation interview questions.


What Are String Methods?

String methods are functions that operate on strings. JavaScript has many built-in.

Built-In String Methods

const str = "Hello";

str.length           // 5
str.toUpperCase()    // "HELLO"
str.toLowerCase()    // "hello"
str.charAt(0)        // "H"
str.indexOf("e")     // 1
str.slice(0, 3)      // "Hel"
str.trim()           // Removes whitespace

These methods are available on every string. You've used them a thousand times.

How Methods Work Conceptually

Every string has methods attached to it. When you call a method, JavaScript:

  1. Takes the string as context (this)
  2. Performs an operation
  3. Returns a result
"hello".toUpperCase()
    ↓
1. this = "hello"
2. Convert all letters to uppercase
3. Return "HELLO"

String Methods Are Just Functions

Under the hood, a string method is just a function that:

  • Accesses the string's characters
  • Does something with them
  • Returns a result

Understanding this is key. Once you know how they work, you can build them.


How Strings Actually Work

Before writing polyfills, understand how strings are structured.

Strings Are Character Arrays

A string is a sequence of characters. You can access each character by index:

const str = "Hello";

str[0]  // "H"
str[1]  // "e"
str[2]  // "l"
str[3]  // "l"
str[4]  // "o"

Each character has a position (index). Index 0 is the first character.

Strings Have a Length

const str = "Hello";
str.length  // 5

You can iterate through all characters:

for (let i = 0; i < str.length; i++) {
  console.log(str[i]);
}

// Output:
// H
// e
// l
// l
// o

Strings Are Immutable

Once created, you can't change a string. Methods return new strings:

const original = "hello";
const upper = original.toUpperCase();

console.log(original); // "hello" (unchanged)
console.log(upper);    // "HELLO" (new string)

This is crucial for polyfills. You always create and return new strings.


What Is a Polyfill?

A polyfill is code that replicates the functionality of a built-in method.

Why Write Polyfills?

Reason 1: Compatibility

Some older browsers don't have newer methods. A polyfill provides that functionality:

// Modern method
const trimmed = "  hello  ".trimStart();

// If the browser doesn't support trimStart(), use a polyfill
function trimStart(str) {
  let start = 0;
  while (str[start] === " ") {
    start++;
  }
  return str.slice(start);
}

Reason 2: Understanding

Writing a polyfill forces you to understand the logic. Interview questions often ask this.

Reason 3: Custom Behavior

Sometimes you need a slight variation:

// Built-in returns the first occurrence
"hello".indexOf("l");  // 2

// Your polyfill could return all occurrences
function findAll(str, char) {
  const indices = [];
  for (let i = 0; i < str.length; i++) {
    if (str[i] === char) {
      indices.push(i);
    }
  }
  return indices;
}

findAll("hello", "l");  // [2, 3]

Polyfill Structure

Every polyfill follows a pattern:

function myPolyfill(input, ...args) {
  // 1. Validate input
  // 2. Initialize variables
  // 3. Iterate through the string (or other logic)
  // 4. Build the result
  // 5. Return the result
}

Let's build some.


Implementing Simple String Utilities

1. Uppercase Polyfill

The logic: Convert each letter to uppercase.

function toUpperCase(str) {
  let result = "";
  
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    // ASCII: lowercase a-z is 97-122, uppercase A-Z is 65-90
    // Difference is 32
    if (char >= "a" && char <= "z") {
      // Convert lowercase to uppercase
      result += String.fromCharCode(char.charCodeAt(0) - 32);
    } else {
      // Keep non-lowercase characters as-is
      result += char;
    }
  }
  
  return result;
}

toUpperCase("hello");     // "HELLO"
toUpperCase("Hello 123"); // "HELLO 123"
toUpperCase("WORLD");     // "WORLD"

How it works:

  1. Loop through each character
  2. Check if it's lowercase (a-z)
  3. If yes, convert to uppercase using ASCII codes
  4. If no, keep it as-is
  5. Build and return the result

2. Lowercase Polyfill

Similar logic, opposite direction:

function toLowerCase(str) {
  let result = "";
  
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    if (char >= "A" && char <= "Z") {
      // Convert uppercase to lowercase
      result += String.fromCharCode(char.charCodeAt(0) + 32);
    } else {
      result += char;
    }
  }
  
  return result;
}

toLowerCase("HELLO");     // "hello"
toLowerCase("Hello 123"); // "hello 123"

3. indexOf Polyfill

Find the first occurrence of a substring:

function indexOf(str, searchStr, fromIndex = 0) {
  // Start from fromIndex (or 0 if not provided)
  for (let i = fromIndex; i < str.length; i++) {
    // Check if searchStr matches starting at position i
    let match = true;
    
    for (let j = 0; j < searchStr.length; j++) {
      if (str[i + j] !== searchStr[j]) {
        match = false;
        break;
      }
    }
    
    if (match) {
      return i; // Found it, return the index
    }
  }
  
  return -1; // Not found
}

indexOf("hello world", "o");      // 4
indexOf("hello world", "world");  // 6
indexOf("hello world", "xyz");    // -1
indexOf("hello hello", "ello", 2); // 5

How it works:

  1. Loop through each position in the string
  2. At each position, check if the substring matches
  3. To check, compare character by character
  4. If all match, return the index
  5. If nothing matches, return -1

4. slice Polyfill

Extract a portion of the string:

function slice(str, start = 0, end = str.length) {
  // Handle negative indices
  if (start < 0) start = Math.max(0, str.length + start);
  if (end < 0) end = Math.max(0, str.length + end);
  
  // Clamp to string boundaries
  start = Math.max(0, start);
  end = Math.min(str.length, end);
  
  let result = "";
  
  for (let i = start; i < end; i++) {
    result += str[i];
  }
  
  return result;
}

slice("hello", 0, 3);    // "hel"
slice("hello", 1);       // "ello"
slice("hello", -3);      // "llo"
slice("hello", -4, -1);  // "ell"

How it works:

  1. Handle negative indices (count from the end)
  2. Clamp values to string boundaries
  3. Loop from start to end
  4. Collect characters and return

5. includes Polyfill

Check if a string contains a substring:

function includes(str, searchStr, position = 0) {
  // Start searching from position
  for (let i = position; i < str.length; i++) {
    // Try to match searchStr at position i
    let match = true;
    
    for (let j = 0; j < searchStr.length; j++) {
      if (str[i + j] !== searchStr[j]) {
        match = false;
        break;
      }
    }
    
    if (match) {
      return true;
    }
  }
  
  return false;
}

includes("hello world", "world");  // true
includes("hello world", "xyz");    // false
includes("hello world", "ell", 1); // true

6. split Polyfill

Split a string by a delimiter:

function split(str, delimiter = "", limit) {
  const result = [];
  
  if (delimiter === "") {
    // Split into individual characters
    for (let i = 0; i < str.length; i++) {
      if (limit !== undefined && result.length >= limit) break;
      result.push(str[i]);
    }
  } else {
    let current = "";
    let i = 0;
    
    while (i < str.length) {
      // Check if delimiter matches at position i
      let match = true;
      for (let j = 0; j < delimiter.length; j++) {
        if (str[i + j] !== delimiter[j]) {
          match = false;
          break;
        }
      }
      
      if (match) {
        // Delimiter found
        result.push(current);
        if (limit !== undefined && result.length >= limit) {
          return result;
        }
        current = "";
        i += delimiter.length;
      } else {
        // Not a match, add character to current
        current += str[i];
        i++;
      }
    }
    
    result.push(current); // Add the last part
  }
  
  return result;
}

split("hello", "");        // ["h", "e", "l", "l", "o"]
split("a,b,c", ",");       // ["a", "b", "c"]
split("hello", "l");       // ["he", "", "o"]
split("a,b,c,d", ",", 2);  // ["a", "b"]

7. trim Polyfill

Remove whitespace from both ends:

function trim(str) {
  let start = 0;
  let end = str.length - 1;
  
  // Find first non-whitespace character
  while (start <= end && str[start] === " ") {
    start++;
  }
  
  // Find last non-whitespace character
  while (end >= start && str[end] === " ") {
    end--;
  }
  
  // Return the trimmed string
  return str.slice(start, end + 1);
}

trim("  hello  ");     // "hello"
trim("hello");         // "hello"
trim("  ");            // ""
trim("  a b c  ");     // "a b c"

How it works:

  1. Find the first non-space character from the left
  2. Find the first non-space character from the right
  3. Extract the substring between them

8. repeat Polyfill

Repeat a string multiple times:

function repeat(str, count) {
  // Validate count
  if (count < 0 || count === Infinity) {
    throw new Error("Invalid count value");
  }
  
  let result = "";
  
  for (let i = 0; i < count; i++) {
    result += str;
  }
  
  return result;
}

repeat("ab", 3);    // "ababab"
repeat("hello", 2); // "hellohello"
repeat("x", 0);     // ""

9. replace Polyfill

Replace the first occurrence of a substring:

function replace(str, searchStr, replaceStr) {
  // Find the first occurrence
  for (let i = 0; i < str.length; i++) {
    let match = true;
    
    // Check if searchStr matches at position i
    for (let j = 0; j < searchStr.length; j++) {
      if (str[i + j] !== searchStr[j]) {
        match = false;
        break;
      }
    }
    
    if (match) {
      // Found it, replace and return
      return str.slice(0, i) + replaceStr + str.slice(i + searchStr.length);
    }
  }
  
  // Not found, return original
  return str;
}

replace("hello world", "world", "universe"); // "hello universe"
replace("hello hello", "hello", "hi");       // "hi hello"
replace("hello", "xyz", "abc");              // "hello"

10. startsWith Polyfill

Check if a string starts with a substring:

function startsWith(str, searchStr, position = 0) {
  // Check if str starts with searchStr from position
  for (let i = 0; i < searchStr.length; i++) {
    if (str[position + i] !== searchStr[i]) {
      return false;
    }
  }
  return true;
}

startsWith("hello", "he");      // true
startsWith("hello", "ell");     // false
startsWith("hello", "ell", 1);  // true
startsWith("hello", "");        // true

String Processing Flow

Here's how string methods conceptually work:

Input String
    ↓
Method receives it
    ↓
Initialize result container
    ↓
Loop through characters
    ↓
    ├─ Evaluate each character
    ├─ Apply logic
    └─ Add to result
    ↓
Return new string

Visual Example: indexOf

String: "hello world"
Search: "o"

Position 0: "h" vs "o" → No match
Position 1: "e" vs "o" → No match
Position 2: "l" vs "o" → No match
Position 3: "l" vs "o" → No match
Position 4: "o" vs "o" → MATCH!

Return: 4

Visual Example: split

String: "a,b,c"
Delimiter: ","

Position 0: "a" → Not delimiter → Add to current
Position 1: "," → DELIMITER → Push "a" to result, reset
Position 2: "b" → Not delimiter → Add to current
Position 3: "," → DELIMITER → Push "b" to result, reset
Position 4: "c" → Not delimiter → Add to current
End of string → Push "c" to result

Result: ["a", "b", "c"]

Common Interview String Problems

Problem 1: Reverse a String

Question: Write a function to reverse a string.

function reverseString(str) {
  let result = "";
  
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  
  return result;
}

reverseString("hello");  // "olleh"
reverseString("12345");  // "54321"

Alternative: Using built-in methods

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

Problem 2: Check for Palindrome

Question: Check if a string is a palindrome.

function isPalindrome(str) {
  // Remove spaces and convert to lowercase
  const clean = str.toLowerCase().replace(/\s/g, "");
  
  // Compare with reversed version
  let reversed = "";
  for (let i = clean.length - 1; i >= 0; i--) {
    reversed += clean[i];
  }
  
  return clean === reversed;
}

isPalindrome("racecar");      // true
isPalindrome("race car");     // true
isPalindrome("hello");        // false

Alternative: Two-pointer approach

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

Problem 3: Count Character Frequency

Question: Count how many times each character appears.

function countCharacters(str) {
  const count = {};
  
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    count[char] = (count[char] || 0) + 1;
  }
  
  return count;
}

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

countCharacters("aabbcc");
// { a: 2, b: 2, c: 2 }

Problem 4: Find the First Non-Repeated Character

Question: Find the first character that appears only once.

function firstNonRepeated(str) {
  const count = {};
  
  // Count all characters
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    count[char] = (count[char] || 0) + 1;
  }
  
  // Find first character with count 1
  for (let i = 0; i < str.length; i++) {
    if (count[str[i]] === 1) {
      return str[i];
    }
  }
  
  return null;
}

firstNonRepeated("hello");      // "h"
firstNonRepeated("aabbcc");     // null
firstNonRepeated("aabbc");      // "c"

Problem 5: Anagram Check

Question: Check if two strings are anagrams.

function isAnagram(str1, str2) {
  // Remove spaces and convert to lowercase
  const clean1 = str1.toLowerCase().replace(/\s/g, "");
  const clean2 = str2.toLowerCase().replace(/\s/g, "");
  
  // Check if lengths match
  if (clean1.length !== clean2.length) {
    return false;
  }
  
  // Count characters in first string
  const count = {};
  for (let i = 0; i < clean1.length; i++) {
    const char = clean1[i];
    count[char] = (count[char] || 0) + 1;
  }
  
  // Check if characters match in second string
  for (let i = 0; i < clean2.length; i++) {
    const char = clean2[i];
    if (!count[char]) {
      return false;
    }
    count[char]--;
  }
  
  return true;
}

isAnagram("listen", "silent");      // true
isAnagram("hello", "world");        // false
isAnagram("abc", "bca");            // true

Alternative: Sorting approach

function isAnagram(str1, str2) {
  const sort1 = str1.toLowerCase().replace(/\s/g, "").split("").sort().join("");
  const sort2 = str2.toLowerCase().replace(/\s/g, "").split("").sort().join("");
  return sort1 === sort2;
}

Problem 6: String Compression

Question: Compress a string by replacing repeated characters with count.

function compress(str) {
  if (str.length <= 2) return str;
  
  let result = "";
  let count = 1;
  
  for (let i = 0; i < str.length; i++) {
    // If next character is different or we're at the end
    if (str[i] !== str[i + 1]) {
      result += str[i] + count;
      count = 1;
    } else {
      count++;
    }
  }
  
  return result.length < str.length ? result : str;
}

compress("aabbcc");      // "a2b2c2"
compress("abcdef");      // "abcdef" (no compression)
compress("aaaa");        // "a4"

Problem 7: Word Frequency

Question: Count how many times each word appears.

function wordFrequency(str) {
  const words = str.toLowerCase().split(/\s+/);
  const count = {};
  
  for (let i = 0; i < words.length; i++) {
    const word = words[i];
    count[word] = (count[word] || 0) + 1;
  }
  
  return count;
}

wordFrequency("hello world hello");
// { hello: 2, world: 1 }

Problem 8: Longest Substring Without Repeating Characters

Question: Find the length of the longest substring without repeating characters.

function longestSubstring(str) {
  let maxLength = 0;
  let currentLength = 0;
  const charIndex = {};
  
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    
    // If character was seen and is in current window
    if (charIndex[char] !== undefined && charIndex[char] >= (i - currentLength)) {
      // Reset length
      currentLength = i - charIndex[char] - 1;
    } else {
      currentLength++;
    }
    
    charIndex[char] = i;
    maxLength = Math.max(maxLength, currentLength);
  }
  
  return maxLength;
}

longestSubstring("abcabcbb");  // 3 ("abc")
longestSubstring("bbbbb");     // 1 ("b")
longestSubstring("pwwkew");    // 3 ("kew")

Why Understanding Built-In Behavior Matters

Interview Preparation

Interviewers ask you to build string methods for a reason:

  1. It shows understanding - You know how strings work at a fundamental level
  2. It tests logic - You can think through problems algorithmically
  3. It reveals gaps - Edge cases in your implementation show what you miss
  4. It's practical - You can debug better when things go wrong

Real-World Application

Understanding how methods work helps you:

  1. Debug faster - When something goes wrong, you understand why
  2. Optimize - You know the cost of operations
  3. Choose wisely - Pick the right method for the job
  4. Extend functionality - Build custom methods when needed

Edge Cases Show Deep Understanding

// A shallow implementation
function includes(str, search) {
  return str.indexOf(search) !== -1;
}

// A deep implementation handles edge cases
function includes(str, search, position = 0) {
  if (search === "") return true;         // Empty string is always included
  if (position >= str.length) return false; // Position out of bounds
  if (search.length > str.length) return false; // Search longer than string
  
  // ... actual logic
}

The difference is understanding the edge cases.


Polyfill Behavior Representation

Built-in Method
    ↓
Input Validation
    ↓
    ├─ Check arguments
    ├─ Handle defaults
    └─ Validate types
    ↓
Core Logic
    ↓
    ├─ Loop through string
    ├─ Apply transformations
    └─ Build result
    ↓
Result Assembly
    ↓
    ├─ Combine parts
    ├─ Format output
    └─ Handle edge cases
    ↓
Return Result

Every polyfill follows this pattern.


String Method Comparison Table

Method Purpose Input Output
toUpperCase Convert to uppercase string string
toLowerCase Convert to lowercase string string
indexOf Find first occurrence string, search, position number
slice Extract substring string, start, end string
includes Check if contains string, search, position boolean
split Split by delimiter string, delimiter, limit array
trim Remove whitespace string string
repeat Repeat string string, count string
replace Replace occurrence string, search, replace string
startsWith Check start string, search, position boolean

Practice Assignment

1. Implement charAt:

function charAt(str, index) {
  // Return the character at the given index
  // Return empty string if out of bounds
}

charAt("hello", 0);  // "h"
charAt("hello", 5);  // ""

2. Implement lastIndexOf:

function lastIndexOf(str, searchStr) {
  // Find the LAST occurrence of searchStr
  // Return -1 if not found
}

lastIndexOf("hello", "l");     // 3
lastIndexOf("hello", "hello"); // 0

3. Implement endsWith:

function endsWith(str, searchStr, length) {
  // Check if str ends with searchStr
  // Optional length parameter
}

endsWith("hello", "lo");   // true
endsWith("hello", "ll");   // false

4. Solve a common problem:

Choose one:

  • Reverse a string
  • Check palindrome
  • Count character frequency
  • Find first non-repeated character
  • Check anagram

5. Implement replaceAll:

function replaceAll(str, searchStr, replaceStr) {
  // Replace ALL occurrences (not just the first)
}

replaceAll("hello hello", "hello", "hi");  // "hi hi"
replaceAll("aaa", "a", "b");                // "bbb"

Common Mistakes

Mistake 1: Forgetting String Immutability

// WRONG - trying to modify a string
let str = "hello";
str[0] = "H";
console.log(str); // "hello" (unchanged)

// RIGHT - create a new string
function capitalize(str) {
  return str[0].toUpperCase() + str.slice(1);
}

Mistake 2: Off-by-One Errors

// WRONG - loop goes one too far
for (let i = 0; i <= str.length; i++) {
  // Accesses str[str.length] which is undefined
}

// RIGHT
for (let i = 0; i < str.length; i++) {
  // Safe access
}

Mistake 3: Not Handling Empty Strings

// WRONG - crashes on empty string
function firstChar(str) {
  return str[0].toUpperCase(); // Error if str is ""
}

// RIGHT
function firstChar(str) {
  if (str.length === 0) return "";
  return str[0].toUpperCase();
}

Mistake 4: Ignoring Edge Cases

// WRONG - doesn't handle negatives
function slice(str, start, end) {
  // What if start is negative?
}

// RIGHT
function slice(str, start = 0, end = str.length) {
  if (start < 0) start = Math.max(0, str.length + start);
  if (end < 0) end = Math.max(0, str.length + end);
  // ... continue
}

Mistake 5: Inefficient Concatenation

// SLOW - creates new string each iteration
function repeat(str, count) {
  let result = "";
  for (let i = 0; i < count; i++) {
    result += str; // Creates new string each time
  }
  return result;
}

// BETTER - still works, same approach is fine for interviews
// For production, consider array join method
function repeat(str, count) {
  let result = "";
  for (let i = 0; i < count; i++) {
    result += str;
  }
  return result;
}

Quick Recap

  • String methods are built-in functions that operate on strings.

  • Strings are sequences of characters that you can access by index.

  • Strings are immutable - methods return new strings, not modify the original.

  • A polyfill replicates the functionality of a built-in method.

  • Why polyfills matter:

    • Understand how built-ins work
    • Prepare for interviews
    • Provide fallbacks for older browsers
  • Core pattern for any polyfill:

    1. Validate input
    2. Initialize result
    3. Loop and process
    4. Return result
  • Common interview problems:

    • Reverse string
    • Palindrome check
    • Character frequency
    • Anagram detection
    • String compression
    • Substring without repeating chars
  • Edge cases matter - empty strings, negative indices, out-of-bounds access.

  • Built-in behavior understanding helps you:

    • Debug faster
    • Choose the right method
    • Optimize code
    • Pass interviews

Build your own string methods. Understand how they work. Master the fundamentals.

Happy coding! 🚀


If you enjoyed this article, check out my other blogs on this profile. 🔗 Connect with me: LinkedIn | GitHub | X (Twitter)