# String Polyfills and Common Interview Methods in JavaScript

Most developers treat string methods like magic. This guide peels back the curtain — you'll implement them from scratch, understand why polyfills exist, and actually be ready when an interviewer says "no built-ins allowed."

## **What is a String Method, Really?**

When you call `"hello".toUpperCase()`, JavaScript isn't doing anything mystical. It's just looking up a function on `String.prototype`. Every string in JS shares that same prototype, which is why all of them get access to `trim`, `includes`, `split`, and the rest.

One thing to burn into your brain early: **strings are immutable**. Every method you call returns a brand-new string — it never touches the original. That's not a quirk, it's the design.

## **Why Write a Polyfill?**

A polyfill is a fallback. When a newer method doesn't exist in an older browser or runtime, a polyfill adds it manually. You've probably used them without knowing — Babel and bundlers inject them all the time.

But the real reason to write them yourself is simpler: it forces you to understand what the method actually does. You can't fake your way through implementing `trim()` — You have to think about what "whitespace" even means, where to start, and where to stop.

> **Interview pattern:**
> 
> Polyfills are a favourite in JS interviews because they expose whether you understand the language or just its surface API. "Implement `split()` without using `split()`" tells an interviewer a lot more than "use `split()` on this array."

The standard shape of any polyfill looks like this — always check first so you don't overwrite a faster native version:  

```javascript
if (!String.prototype.methodName) {
  String.prototype.methodName = function() {
    // your implementation here
  };
}
```

![](https://cdn.hashnode.com/uploads/covers/696f9f5589255b82f81f6b17/f5bf6cf4-306a-470d-b43e-82670859ed1e.png align="center")

## **Core Polyfills, Built from Scratch**

### **1\. trim()**

Two pointers — one from the left, one from the right. Walk them inward until you hit a non-whitespace character, then slice between them. Clean, O(n), and easy to explain.

```javascript
if (!String.prototype.trim) {
  String.prototype.trim = function() {
    let start = 0;
    let end = this.length - 1;

    while ([' ', '\n', '\t'].includes(this[start])) start++;
    while ([' ', '\n', '\t'].includes(this[end]))   end--;

    return this.slice(start, end + 1);
  };
}
```

### **2\. includes()**

Slide a window of the same length as your search string across the main string. At each position, check if the slice matches. Return `true` the moment it does, `false` if you reach the end.

```javascript
if (!String.prototype.includes) {
  String.prototype.includes = function(search, start = 0) {
    if (search instanceof RegExp)
      throw new TypeError('First argument must not be a RegExp');

    for (let i = start; i <= this.length - search.length; i++) {
      if (this.slice(i, i + search.length) === search) return true;
    }
    return false;
  };
}
```

### **3\. repeat()**

Straightforward loop — concatenate the string onto itself *count* times. Just validate the edge cases first.

```javascript
if (!String.prototype.repeat) {
  String.prototype.repeat = function(count) {
    if (count < 0 || count === Infinity)
      throw new RangeError('Invalid count value');

    let result = '';
    count = Math.floor(count);
    for (let i = 0; i < count; i++) result += this;
    return result;
  };
}
```

### **4\. split() — the interview classic**

This one trips people up because of the edge cases: empty separator, undefined separator, and the limit parameter. Work through each case explicitly.

```javascript
if (!String.prototype.split) {
  String.prototype.split = function(separator, limit) {
    const result = [];

    if (separator === '') {
      for (let i = 0; i < this.length; i++) {
        if (limit !== undefined && result.length >= limit) break;
        result.push(this[i]);
      }
      return result;
    }

    if (separator === undefined) return [this.toString()];

    let curr = '';
    const sepLen = separator.length;

    for (let i = 0; i < this.length; i++) {
      if (limit !== undefined && result.length >= limit) return result;
      if (this.slice(i, i + sepLen) === separator) {
        result.push(curr);
        curr = '';
        i += sepLen - 1;
      } else { curr += this[i]; }
    }

    if (limit === undefined || result.length < limit) result.push(curr);
    return result;
  };
}
```

## **How split() Actually Works**

Here's what's happening character by character when you call `"a,b,c".split(",")`:

![](https://cdn.hashnode.com/uploads/covers/696f9f5589255b82f81f6b17/b1199954-c8cc-480c-bf3d-9416625c6970.png align="center")

  
**Classic Interview Problems**

These come up constantly. The polyfill thinking — iterating character by character, tracking state manually — applies directly to all of them.

| Problem | Core idea |
| --- | --- |
| reverse() | Loop from `length-1` down to 0, build a new string |
| isPalindrome() | Two pointers — compare `str[i]` vs `str[n-1-i]` until midpoint |
| capitalize() | Check ASCII range 97–122 (a–z), subtract 32 for uppercase |
| countVowels() | Loop each char, check if `'aeiou'` contains it |
| removeDupes() | Track seen characters in an object/Set, only add unseen ones |
| truncate() | `slice(0, max)` + append `'...'` if needed |

**Always state these three things** for every interview solution: time complexity (usually O(n) for a single pass), space complexity (O(n) for the new string you're building), and the edge cases you've handled — empty string, single character, all whitespace.

## **A Deeper Look: Capitalize Without Built-ins**

This comparison shows exactly what polyfill thinking buys you. Compare the two approaches to capitalizing a word:

```plaintext
// Surface-level: works, but you don't know why
const cap1 = word.charAt(0).toUpperCase() + word.slice(1);

// Deeper: you know what toUpperCase() is actually doing
const code = word.charCodeAt(0);
const cap2 = (code >= 97 && code <= 122)
  ? String.fromCharCode(code - 32) + word.slice(1)
  : word;
```

The second version isn't better for production code — the first is perfectly fine. But understanding *why* subtracting 32 works (because uppercase letters occupy ASCII positions 65–90, exactly 32 below their lowercase equivalents at 97–122) is the difference between using the language and knowing it.
