Skip to main content

Command Palette

Search for a command to run...

String Polyfills and Common Interview Methods in JavaScript

Updated
14 min read
String Polyfills and Common Interview Methods in JavaScript

You know that one friend who always says "I know the shortcut to everything"? JavaScript's built-in string methods are exactly that friend. They work great. But here's the thing most developers skip: do you actually know what's happening under the hood? And if those methods didn't exist, could you build them yourself?

That last question? That's exactly what interviewers love to ask.

This post is about two things: understanding how common string methods work conceptually, and learning how to write polyfills for them. If you're preparing for interviews, this is one of those topics that separates the "I use it" crowd from the "I understand it" crowd.

Let's get into it.

What Is a String Method?

A string method is a built-in function that JavaScript gives you to work with string values. When you write "hello".toUpperCase(), you're calling a method that lives on String.prototype. That means every string you create in JavaScript has access to these methods automatically.

const name = "ami";
console.log(name.toUpperCase()); // "AMI"

The name variable is a primitive string, but JavaScript temporarily wraps it in a String object so you can call methods on it. This is called autoboxing. You don't need to think about it too much, just know that's why "hello".length works even though "hello" is not an object.

String methods do not modify the original string. Strings in JavaScript are immutable. Every method returns a new value.

const word = "chai";
const upper = word.toUpperCase();

console.log(word);  // "chai" -- untouched
console.log(upper); // "CHAI"

What Is a Polyfill?

A polyfill is code you write yourself to replicate a feature that either:

  1. Does not exist in older environments (like old browsers), or

  2. An interviewer has removed from existence to test you

The word "polyfill" comes from a UK product called Polyfilla, which you use to fill cracks in a wall. Same idea. You're filling in gaps.

In real-world web development, polyfills were extremely important during the years when browsers were not consistent with each other. IE11, for example, did not support many ES6+ methods. Developers had to write their own implementations or use libraries like core-js that polyfill missing features automatically.

Today, modern browsers are much better aligned. But polyfills still matter for:

  • Interview preparation (the number one reason if you're job hunting)

  • Deep understanding of how methods actually work

  • Embedded/restricted environments where you cannot depend on the full browser API

Writing a Polyfill: The Pattern

Before writing any polyfill, you follow a simple pattern. Check if the method already exists. If it does, leave it alone. If not, define it.

if (!String.prototype.methodName) {
  String.prototype.methodName = function(/* args */) {
    // your implementation
  };
}

The this keyword inside a method on String.prototype refers to the string the method is being called on. Keep that in mind throughout this post.

Method 1: includes()

What it does

includes() checks whether a string contains a given substring. It returns true or false.

const sentence = "Maa ne kaha homework karo";
console.log(sentence.includes("homework")); // true
console.log(sentence.includes("Netflix"));  // false

It also accepts an optional second argument: the position to start searching from.

const s = "abcabc";
console.log(s.includes("a", 1)); // true -- found at index 3
console.log(s.includes("a", 4)); // false -- no "a" from index 4 onwards

Conceptual logic

To find a substring inside a string, you can check character by character. For each position in the main string, check if the substring starts there. If any position matches, return true.

The Polyfill

if (!String.prototype.myIncludes) {
  String.prototype.myIncludes = function(searchString, position) {
    const str = String(this);
    const search = String(searchString);
    const startIndex = position !== undefined ? Math.max(0, position) : 0;

    for (let i = startIndex; i <= str.length - search.length; i++) {
      let match = true;
      for (let j = 0; j < search.length; j++) {
        if (str[i + j] !== search[j]) {
          match = false;
          break;
        }
      }
      if (match) return true;
    }

    return false;
  };
}

// Test it
console.log("Maa ne kaha homework karo".myIncludes("homework")); // true
console.log("abcabc".myIncludes("a", 4));                        // false

The inner loop checks character by character whether the substring matches at position i. If all characters line up, it returns true. If we exhaust the string without a match, we return false.

Method 2: startsWith()

What it does

startsWith() checks if a string begins with a given substring.

const msg = "Please submit your assignment";
console.log(msg.startsWith("Please")); // true
console.log(msg.startsWith("submit")); // false

Like includes(), it accepts an optional second argument to specify where to start looking from.

console.log("hello world".startsWith("world", 6)); // true

The Polyfill

if (!String.prototype.myStartsWith) {
  String.prototype.myStartsWith = function(searchString, position) {
    const str = String(this);
    const search = String(searchString);
    const startIndex = position !== undefined ? Math.max(0, position) : 0;

    if (search.length > str.length - startIndex) return false;

    for (let i = 0; i < search.length; i++) {
      if (str[startIndex + i] !== search[i]) return false;
    }

    return true;
  };
}

// Test
console.log("Please submit".myStartsWith("Please")); // true
console.log("hello world".myStartsWith("world", 6)); // true

Simple loop. Start at startIndex, compare each character of the search string. If any mismatch, return false. If the loop completes, return true.

Method 3: endsWith()

What it does

endsWith() checks if a string ends with a given substring.

const filename = "resume_final_FINAL_v3.pdf";
console.log(filename.endsWith(".pdf")); // true
console.log(filename.endsWith(".doc")); // false

It also accepts an optional second argument that acts as the effective length of the string.

console.log("hello".endsWith("hell", 4)); // true -- treats string as "hell"

The Polyfill

if (!String.prototype.myEndsWith) {
  String.prototype.myEndsWith = function(searchString, endPosition) {
    const str = String(this);
    const search = String(searchString);
    const end = endPosition !== undefined
      ? Math.min(Math.max(endPosition, 0), str.length)
      : str.length;

    const startIndex = end - search.length;

    if (startIndex < 0) return false;

    for (let i = 0; i < search.length; i++) {
      if (str[startIndex + i] !== search[i]) return false;
    }

    return true;
  };
}

// Test
console.log("resume_final.pdf".myEndsWith(".pdf")); // true
console.log("hello".myEndsWith("hell", 4));          // true

The trick here is working backwards. Calculate where the search string should start if the main string ended at endPosition. Then compare forward from that point.

Method 4: repeat()

What it does

repeat() returns a new string with the original string repeated a given number of times.

console.log("na".repeat(4) + " Batman!"); // "nananana Batman!"
console.log("chai".repeat(3));             // "chaichaichai"

If you pass 0, it returns an empty string. Negative numbers throw a RangeError.

The Polyfill

if (!String.prototype.myRepeat) {
  String.prototype.myRepeat = function(count) {
    const str = String(this);
    const n = Math.floor(count);

    if (n < 0 || n === Infinity) {
      throw new RangeError("Invalid count value");
    }

    let result = "";
    for (let i = 0; i < n; i++) {
      result += str;
    }

    return result;
  };
}

// Test
console.log("na".myRepeat(4) + " Batman!"); // "nananana Batman!"
console.log("chai".myRepeat(0));             // ""

No magic here. Just loop n times and concatenate the string to itself.

Note: For very large values of n, this loop-based approach is slow. A smarter approach uses the "exponentiation by squaring" technique where you double the string repeatedly. But for an interview, the simple loop is perfectly acceptable and readable.

Method 5: padStart()

What it does

padStart() pads the beginning of a string with a given character until the string reaches a specified total length.

const phoneNumber = "98765";
console.log(phoneNumber.padStart(10, "*")); // "*****98765"

const hour = "9";
console.log(hour.padStart(2, "0")); // "09"

If no pad character is provided, it defaults to a space.

The Polyfill

if (!String.prototype.myPadStart) {
  String.prototype.myPadStart = function(targetLength, padString) {
    const str = String(this);
    const padChar = padString !== undefined ? String(padString) : " ";
    const totalLength = Math.floor(targetLength);

    if (str.length >= totalLength) return str;

    const padNeeded = totalLength - str.length;
    let padding = "";

    while (padding.length < padNeeded) {
      padding += padChar;
    }

    return padding.slice(0, padNeeded) + str;
  };
}

// Test
console.log("9".myPadStart(2, "0"));  // "09"
console.log("98765".myPadStart(10, "*")); // "*****98765"

Build the padding by repeating the pad character in a while loop. Slice it to the exact amount needed, then attach it before the original string.

Method 6: padEnd()

What it does

Same as padStart(), but pads the end instead.

console.log("Loading".padEnd(10, ".")); // "Loading..."

The Polyfill

if (!String.prototype.myPadEnd) {
  String.prototype.myPadEnd = function(targetLength, padString) {
    const str = String(this);
    const padChar = padString !== undefined ? String(padString) : " ";
    const totalLength = Math.floor(targetLength);

    if (str.length >= totalLength) return str;

    const padNeeded = totalLength - str.length;
    let padding = "";

    while (padding.length < padNeeded) {
      padding += padChar;
    }

    return str + padding.slice(0, padNeeded);
  };
}

// Test
console.log("Loading".myPadEnd(10, ".")); // "Loading..."

Identical logic to padStart(). The only difference is where the padding gets attached: at the end.

Method 7: trim(), trimStart(), and trimEnd()

What they do

trim() removes whitespace from both ends of a string. trimStart() removes it from the beginning only. trimEnd() removes it from the end only.

const messy = "   hello world   ";
console.log(messy.trim());      // "hello world"
console.log(messy.trimStart()); // "hello world   "
console.log(messy.trimEnd());   // "   hello world"

The Polyfill for trim()

if (!String.prototype.myTrim) {
  String.prototype.myTrim = function() {
    const str = String(this);
    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);
  };
}

console.log("   hello world   ".myTrim()); // "hello world"

Two pointers. One from the left, one from the right. Move them inward as long as they're pointing at spaces. Slice what's in between.

Note: Actual whitespace includes tabs (\t), newlines (\n), and more. A production polyfill would check for all of these. For an interview, clarifying this shows you understand the detail.

Method 8: replaceAll()

What it does

replaceAll() was introduced in ES2021. It replaces every occurrence of a substring, not just the first one (which is what replace() does by default).

const text = "eat sleep code repeat eat sleep";
console.log(text.replace("eat", "debug"));    // "debug sleep code repeat eat sleep"
console.log(text.replaceAll("eat", "debug")); // "debug sleep code repeat de

The Polyfill

if (!String.prototype.myReplaceAll) {
  String.prototype.myReplaceAll = function(searchValue, replaceValue) {
    const str = String(this);
    const search = String(searchValue);
    const replacement = String(replaceValue);

    if (search === "") {
      // Edge case: inserting replacement between every character
      return str.split("").join(replacement);
    }

    let result = "";
    let i = 0;

    while (i < str.length) {
      let match = true;

      if (i + search.length > str.length) {
        result += str[i];
        i++;
        continue;
      }

      for (let j = 0; j < search.length; j++) {
        if (str[i + j] !== search[j]) {
          match = false;
          break;
        }
      }

      if (match) {
        result += replacement;
        i += search.length;
      } else {
        result += str[i];
        i++;
      }
    }

    return result;
  };
}

// Test
console.log("eat sleep eat".myReplaceAll("eat", "debug")); // "debug sleep debug"

Walk through the string. When the current position matches the search string, append the replacement and jump ahead by the search string's length. Otherwise, just copy the character and move one step forward.

Common Interview Problems

Now let's shift gears. Beyond polyfills, interviewers also love to ask string manipulation problems from scratch. Here are a few that come up frequently.

Problem 1: Reverse a String

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

console.log(reverseString("javascript")); // "tpircsavaj"

The shortcut version uses built-in methods:

const reversed = "javascript".split("").reverse().join("");

In an interview, knowing both approaches is ideal. The loop version shows you understand the logic. The one-liner shows you know the language.

Problem 2: Check If a String Is a Palindrome

A palindrome is a string that reads the same forwards and backwards. Like "madam" or "racecar".

function isPalindrome(str) {
  const clean = str.toLowerCase().replace(/[^a-z0-9]/g, "");
  const reversed = clean.split("").reverse().join("");
  return clean === reversed;
}

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

The replace with the regex removes spaces and punctuation first. This handles edge cases that catch a lot of candidates off guard.

Problem 3: Count Occurrences of a Character

function countOccurrences(str, char) {
  let count = 0;
  for (let i = 0; i < str.length; i++) {
    if (str[i] === char) count++;
  }
  return count;
}

console.log(countOccurrences("banana", "a")); // 3
console.log(countOccurrences("mississippi", "s")); // 4

Problem 4: Find the First Non-Repeating Character

function firstNonRepeating(str) {
  const freq = {};

  for (let char of str) {
    freq[char] = (freq[char] || 0) + 1;
  }

  for (let char of str) {
    if (freq[char] === 1) return char;
  }

  return null;
}

console.log(firstNonRepeating("aabbcde")); // "c"
console.log(firstNonRepeating("aabb"));    // null

Two passes. First, count how many times each character appears. Second, go through the string again in order and return the first one with a count of exactly 1.

Problem 5: Check If Two Strings Are Anagrams

Two strings are anagrams if they contain the same characters in the same frequency, just in a different order.

function areAnagrams(str1, str2) {
  if (str1.length !== str2.length) return false;

  const freq = {};

  for (let char of str1.toLowerCase()) {
    freq[char] = (freq[char] || 0) + 1;
  }

  for (let char of str2.toLowerCase()) {
    if (!freq[char]) return false;
    freq[char]--;
  }

  return true;
}

console.log(areAnagrams("listen", "silent")); // true
console.log(areAnagrams("hello", "world"));   // false

Build a frequency map from the first string. Then for each character in the second string, decrement the count. If a character is missing or the count goes negative, they're not anagrams.

Problem 6: Truncate a String (Without slice())

Sometimes interviewers ask you to do things without the obvious built-in.

function truncate(str, maxLength) {
  if (str.length <= maxLength) return str;

  let result = "";
  for (let i = 0; i < maxLength; i++) {
    result += str[i];
  }

  return result + "...";
}

console.log(truncate("Ek bar padh ke aaja please", 10)); 
// "Ek bar padh ke..."

Why Understanding Built-in Behavior Matters

You might be thinking: why do I need to know all this when the methods already exist?

Here's the honest answer.

If you use includes() without knowing what it does internally, you might misuse it. You might assume it's O(1) when it's actually O(n * m) in the worst case. You might not know what happens when you pass undefined or null as the search argument. You might not handle edge cases correctly in your own logic.

Understanding the built-in behavior means:

  1. You write better code. You know what to expect from each method.

  2. You ace interviews. Interviewers are specifically testing whether you can think algorithmically, not just recall method names.

  3. You debug faster. When something breaks, you know exactly where to look.

And there's a bonus: once you've written a polyfill for padStart(), you'll never forget how it works. It sticks.

A Quick Note on this in Polyfills

When you attach a function to String.prototype, the this keyword inside that function refers to the string the method is called on. But there's a catch: if you use an arrow function, this will not work correctly because arrow functions do not have their own this.

// WRONG -- arrow function, `this` will be undefined or wrong
String.prototype.myTrim = () => {
  const str = String(this); // `this` is not the string here
};

// CORRECT -- regular function, `this` is the string
String.prototype.myTrim = function() {
  const str = String(this); // works as expected
};

This is one of those small things that will definitely come up in interviews if you're explaining your polyfill live. Mention it confidently.