Understanding how string methods work under the hood — and building them yourself.
Here's something that changed how I think about JavaScript: anyone can use .trim() or .includes(). You call the method, it works, you move on. But can you build it from scratch? Can you explain what happens inside when you call .split() or .repeat()?
That's exactly what interviewers want to know. Not whether you've memorized the API, but whether you understand the logic behind it. And the best way to prove that? Write the method yourself — a polyfill.
In the ChaiCode Web Dev Cohort 2026, this shift from "using tools" to "understanding tools" was a turning point. Let me walk you through the most common string methods, how they work conceptually, and how to build your own versions.
What Are String Methods?
String methods are built-in functions that every string in JavaScript comes with. They let you manipulate, search, transform, and inspect text without writing manual loops.
const name = " Pratham Bhardwaj ";
console.log(name.trim()); // "Pratham Bhardwaj"
console.log(name.toUpperCase()); // " PRATHAM BHARDWAJ "
console.log(name.includes("Pratham")); // true
console.log(name.split(" ")); // ["", "", "Pratham", "Bhardwaj", "", ""]
One line, one method, one result. But what's happening inside each of these calls? That's what we're going to explore.
Strings Are Immutable
Before we go further, remember this: strings in JavaScript cannot be changed. Every string method returns a new string — the original stays untouched.
const greeting = "hello";
const shouted = greeting.toUpperCase();
console.log(greeting); // "hello" — unchanged
console.log(shouted); // "HELLO" — new string
This is important for polyfills — your implementations should always return new values, never mutate the input.
Why Do Developers Write Polyfills?
A polyfill is a piece of code that provides the functionality of a newer feature in environments that don't natively support it. But beyond browser compatibility, writing polyfills serves two bigger purposes:
1. Deep Understanding
When you build .trim() from scratch, you have to understand exactly what it does: strip whitespace from both ends, leave everything in the middle alone. You can't fake that understanding.
2. Interview Preparation
"Implement String.prototype.includes" is a real interview question. Interviewers don't care that the method already exists. They want to see:
- Can you break a problem into steps?
- Do you understand string indexing and iteration?
- Can you handle edge cases?
The Polyfill Mental Model
Built-in method: "hello".includes("ell") → true
↓
(black box — you call it, it works)
Polyfill: "hello".includes("ell") → true
↓
Your code:
- Loop through each position
- At each position, check if substring matches
- If found, return true
- If loop ends, return false
Same input → same output. You just built the black box.
Common String Methods — Concept + Polyfill
Let's go through the most commonly asked string methods. For each one, I'll explain what it does, show the built-in version, and then build it from scratch.
1. trim() — Remove Whitespace from Both Ends
What it does: Removes spaces, tabs, and newlines from the start and end of a string.
// Built-in
console.log(" hello ".trim()); // "hello"
console.log("\t hi \n".trim()); // "hi"
Polyfill:
const myTrim = (str) => {
let start = 0;
let end = str.length - 1;
// Move start forward past whitespace
while (start <= end && str[start] === " ") {
start++;
}
// Move end backward past whitespace
while (end >= start && str[end] === " ") {
end--;
}
// Extract the substring between start and end (inclusive)
return str.slice(start, end + 1);
};
console.log(myTrim(" hello ")); // "hello"
console.log(myTrim(" ")); // ""
console.log(myTrim("no spaces")); // "no spaces"
How it works: Two pointers — one from the left, one from the right — move inward past whitespace characters. Whatever's between them is the trimmed result.
Input: " hello "
↑ ↑
start end
Step 1: start moves right past spaces → lands on 'h'
Step 2: end moves left past spaces → lands on 'o'
Result: str.slice(start, end + 1) → "hello"
2. includes() — Check If a Substring Exists
What it does: Returns true if the string contains the specified substring, false otherwise.
// Built-in
console.log("JavaScript".includes("Script")); // true
console.log("JavaScript".includes("Python")); // false
Polyfill:
const myIncludes = (str, target) => {
if (target.length > str.length) return false;
for (let i = 0; i <= str.length - target.length; i++) {
if (str.slice(i, i + target.length) === target) {
return true;
}
}
return false;
};
console.log(myIncludes("JavaScript", "Script")); // true
console.log(myIncludes("JavaScript", "Python")); // false
console.log(myIncludes("hello", "")); // true (empty string is always found)
How it works: Slide a window of target.length across the string. At each position, extract a chunk and compare it to the target.
String: "JavaScript"
Target: "Script" (length: 6)
Position 0: "JavaSc" === "Script"? No
Position 1: "avaSci" === "Script"? No
Position 2: "vaScrip" — wait, let me recalculate
Position 4: "Script" === "Script"? ✅ Yes → return true
3. repeat() — Repeat a String N Times
What it does: Returns a new string with the original repeated n times.
// Built-in
console.log("ha".repeat(3)); // "hahaha"
console.log("⭐".repeat(5)); // "⭐⭐⭐⭐⭐"
Polyfill:
const myRepeat = (str, count) => {
if (count <= 0) return "";
let result = "";
for (let i = 0; i < count; i++) {
result += str;
}
return result;
};
console.log(myRepeat("ha", 3)); // "hahaha"
console.log(myRepeat("abc", 0)); // ""
console.log(myRepeat("-", 10)); // "----------"
How it works: Start with an empty string. Loop count times, concatenating the original string each time.
myRepeat("ha", 3):
i=0: result = "" + "ha" → "ha"
i=1: result = "ha" + "ha" → "haha"
i=2: result = "haha" + "ha" → "hahaha"
Return: "hahaha"
4. startsWith() and endsWith() — Check Beginning/End
What they do: Check if a string starts or ends with a given substring.
// Built-in
console.log("Pratham".startsWith("Pra")); // true
console.log("Pratham".endsWith("ham")); // true
Polyfills:
const myStartsWith = (str, target) => {
if (target.length > str.length) return false;
return str.slice(0, target.length) === target;
};
const myEndsWith = (str, target) => {
if (target.length > str.length) return false;
return str.slice(str.length - target.length) === target;
};
console.log(myStartsWith("Pratham", "Pra")); // true
console.log(myStartsWith("Pratham", "ham")); // false
console.log(myEndsWith("Pratham", "ham")); // true
console.log(myEndsWith("Pratham", "Pra")); // false
How it works: Extract a chunk from the beginning (or end) with the same length as the target. Compare directly.
myStartsWith("Pratham", "Pra"):
str.slice(0, 3) → "Pra"
"Pra" === "Pra" → true ✅
myEndsWith("Pratham", "ham"):
str.slice(7 - 3) → str.slice(4) → "ham"
"ham" === "ham" → true ✅
5. indexOf() — Find the Position of a Substring
What it does: Returns the index of the first occurrence of a substring. Returns -1 if not found.
// Built-in
console.log("JavaScript".indexOf("Script")); // 4
console.log("JavaScript".indexOf("Python")); // -1
Polyfill:
const myIndexOf = (str, target) => {
if (target === "") return 0;
if (target.length > str.length) return -1;
for (let i = 0; i <= str.length - target.length; i++) {
if (str.slice(i, i + target.length) === target) {
return i;
}
}
return -1;
};
console.log(myIndexOf("JavaScript", "Script")); // 4
console.log(myIndexOf("JavaScript", "Java")); // 0
console.log(myIndexOf("JavaScript", "Python")); // -1
console.log(myIndexOf("hello hello", "hello")); // 0 (first occurrence)
Notice how similar this is to includes()? The logic is nearly identical — the only difference is that includes() returns true/false while indexOf() returns the position.
6. charAt() — Get Character at Index
What it does: Returns the character at a given index.
// Built-in
console.log("Pratham".charAt(0)); // "P"
console.log("Pratham".charAt(3)); // "t"
Polyfill:
const myCharAt = (str, index) => {
if (index < 0 || index >= str.length) return "";
return str[index];
};
console.log(myCharAt("Pratham", 0)); // "P"
console.log(myCharAt("Pratham", 6)); // "m"
console.log(myCharAt("Pratham", 10)); // ""
This one's almost too simple — but interviewers use it as a warm-up before harder questions.
Common Interview String Problems
Beyond polyfills, these are the string problems that show up constantly in interviews:
1. Reverse a String
const reverseString = (str) => {
let reversed = "";
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
};
console.log(reverseString("hello")); // "olleh"
console.log(reverseString("JavaScript")); // "tpircSavaJ"
Or the one-liner:
const reverseString = (str) => str.split("").reverse().join("");
Interview tip: Know both approaches. The loop shows you understand the logic. The one-liner shows you know the language.
2. Check If a String Is a Palindrome
const isPalindrome = (str) => {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, "");
let left = 0;
let right = cleaned.length - 1;
while (left < right) {
if (cleaned[left] !== cleaned[right]) return false;
left++;
right--;
}
return true;
};
console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("Race Car")); // true
console.log(isPalindrome("hello")); // false
"racecar" — check from both ends:
r ← → r ✅
a ← → a ✅
c ← → c ✅
e (center — done)
Result: true (it's a palindrome)
3. Count Character Occurrences
const charCount = (str) => {
const counts = {};
for (const char of str) {
counts[char] = (counts[char] || 0) + 1;
}
return counts;
};
console.log(charCount("hello"));
// { h: 1, e: 1, l: 2, o: 1 }
console.log(charCount("javascript"));
// { j: 1, a: 2, v: 1, s: 1, c: 1, r: 1, i: 1, p: 1, t: 1 }
4. Capitalize First Letter of Each Word
const capitalizeWords = (str) => {
return str
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
};
console.log(capitalizeWords("hello world")); // "Hello World"
console.log(capitalizeWords("javaScript is FUN")); // "Javascript Is Fun"
5. Check If Two Strings Are Anagrams
const isAnagram = (str1, str2) => {
const normalize = (s) => s.toLowerCase().split("").sort().join("");
return normalize(str1) === normalize(str2);
};
console.log(isAnagram("listen", "silent")); // true
console.log(isAnagram("hello", "world")); // false
console.log(isAnagram("Dormitory", "Dirty Room")); // false (spaces matter here)
Why Understanding Built-In Behavior Matters
Let me be direct about this: knowing how to use string methods isn't enough anymore.
In interviews, knowing that .includes() exists gets you zero points. But explaining that it works by sliding a window across the string and comparing substrings at each position? That shows you think like a developer.
Here's what building polyfills teaches you:
| Skill | How Polyfills Build It |
|---|---|
| String indexing | You manually access str[i] and work with positions |
| Loop construction | You build for loops with correct boundaries |
| Edge case thinking | Empty strings, out-of-bounds indexes, targets longer than source |
| Algorithm design | Sliding window, two pointers, accumulation patterns |
| Problem decomposition | Breaking "trim this string" into concrete, implementable steps |
These aren't just interview skills — they're engineering skills. Every complex feature you'll ever build requires breaking problems into steps, handling edge cases, and understanding what your tools are doing under the surface.
String Processing Flow — The General Pattern
Most string polyfills follow the same general flow:
┌─────────────────────┐
│ Receive input │
│ (string + args) │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ Handle edge cases │
│ (empty, null, etc) │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ Iterate through │
│ the string │
│ (char by char or │
│ window by window) │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ Apply logic at │
│ each position │
│ (compare, count, │
│ accumulate) │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ Return result │
│ (new string, index,│
│ boolean) │
└─────────────────────┘
Once you see this pattern, every string polyfill becomes a variation of the same structure. The only thing that changes is the logic in the middle.
Let's Practice: Hands-On Assignment
Part 1: Build myTrim()
// Implement your own trim function
const myTrim = (str) => {
let start = 0;
let end = str.length - 1;
while (start <= end && str[start] === " ") start++;
while (end >= start && str[end] === " ") end--;
return str.slice(start, end + 1);
};
// Test it
console.log(myTrim(" hello world ")); // "hello world"
console.log(myTrim("no spaces")); // "no spaces"
console.log(myTrim(" ")); // ""
Part 2: Build myIncludes()
const myIncludes = (str, target) => {
for (let i = 0; i <= str.length - target.length; i++) {
if (str.slice(i, i + target.length) === target) return true;
}
return false;
};
// Test it
console.log(myIncludes("ChaiCode", "Chai")); // true
console.log(myIncludes("ChaiCode", "Tea")); // false
Part 3: Reverse a String Without Built-In Methods
const myReverse = (str) => {
let result = "";
for (let i = str.length - 1; i >= 0; i--) {
result += str[i];
}
return result;
};
console.log(myReverse("Pratham")); // "mahtarP"
console.log(myReverse("12345")); // "54321"
Part 4: Count Vowels in a String
const countVowels = (str) => {
const vowels = "aeiouAEIOU";
let count = 0;
for (const char of str) {
if (vowels.includes(char)) count++;
}
return count;
};
console.log(countVowels("Pratham Bhardwaj")); // 4
console.log(countVowels("JavaScript")); // 3
Key Takeaways
- String methods are built-in functions that transform, search, and inspect text. They always return new values — strings are immutable.
- Polyfills are custom implementations of built-in features. Writing them proves you understand the logic, not just the API.
- Most string polyfills follow the same pattern: handle edge cases → iterate through the string → apply logic → return result.
- Common interview problems — reverse, palindrome, anagram, character count — all build on the same fundamental skills: string indexing, loops, and comparison.
-
Understanding beats memorizing. Knowing how
.includes()works makes you a better debugger, a better problem solver, and a stronger interview candidate.
Wrapping Up
There's a big difference between a developer who uses string methods and a developer who understands them. Polyfills bridge that gap. They force you to think about what actually happens when you call .trim() or .indexOf() — and that kind of thinking is exactly what separates junior developers from strong ones.
I'm building this depth of understanding through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. Writing polyfills has been one of the most valuable exercises so far — not because I'll use them in production (I won't, the built-in methods are great), but because the process of building them made me a significantly better problem solver.
Connect with me on LinkedIn or visit PrathamDEV.in. More articles coming as I keep leveling up.
Happy coding! 🚀
Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode
Top comments (0)