String Polyfills and Common Interview Methods in JavaScript

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:
- Takes the string as context (
this) - Performs an operation
- 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:
- Loop through each character
- Check if it's lowercase (a-z)
- If yes, convert to uppercase using ASCII codes
- If no, keep it as-is
- 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:
- Loop through each position in the string
- At each position, check if the substring matches
- To check, compare character by character
- If all match, return the index
- 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:
- Handle negative indices (count from the end)
- Clamp values to string boundaries
- Loop from start to end
- 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:
- Find the first non-space character from the left
- Find the first non-space character from the right
- 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:
- It shows understanding - You know how strings work at a fundamental level
- It tests logic - You can think through problems algorithmically
- It reveals gaps - Edge cases in your implementation show what you miss
- It's practical - You can debug better when things go wrong
Real-World Application
Understanding how methods work helps you:
- Debug faster - When something goes wrong, you understand why
- Optimize - You know the cost of operations
- Choose wisely - Pick the right method for the job
- 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:
- Validate input
- Initialize result
- Loop and process
- 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)




