String Polyfills and Common Interview Methods in JavaScript

Hello readers 👋, welcome to the 24th blog in this JavaScript series!

Last time we explored the clever spread and rest operators, learning how the same three dots can either unpack or collect data. Today we are shifting gears to a topic that deeply sharpens your understanding of how JavaScript works under the hood: string polyfills and common interview methods.

Have you ever wondered how "hello".includes("ell") works inside the engine? Or how you could make the same behavior work in an old browser that doesn’t support includes? That’s exactly what we are going to dive into. We will not just use string methods, we will build several of them from scratch, understand the logic, and then solve some classic interview string problems.

Let’s get our hands dirty.

What string methods are

In JavaScript, every string is a primitive value, but when you access a property or method on it, the engine wraps it in a String object behind the scenes. This gives you access to a large set of built-in methods like indexOf, slice, substring, includes, startsWith, endsWith, trim, repeat, and many more.

These methods help you manipulate and inspect strings without manually looping through characters. For example:

const greeting = "Hello, Satya";
console.log(greeting.includes("Satya")); // true
console.log(greeting.startsWith("Hello")); // true
console.log(greeting.endsWith("ya")); // true
console.log(greeting.repeat(2)); // "Hello, SatyaHello, Satya"

They are convenient, but they are also abstractions over simple character-by-character operations. Understanding what happens under the hood is not only fascinating but also extremely valuable for technical interviews.

Why developers write polyfills

A polyfill is a piece of code that provides modern functionality to older browsers that lack it. For example, String.prototype.includes was introduced in ES6 (2015). If you needed to support an environment that didn’t have it, you would write your own version of includes and attach it to String.prototype (carefully, of course).

But even in modern development, writing polyfills serves another purpose: it forces you to truly understand how a method works. Interviewers love to ask, “Can you implement your own version of startsWith?” or “How would you write a polyfill for repeat?” Knowing the internal logic makes you a stronger developer and prepares you for these moments.

So, let’s start building.

Implementing simple string utilities: polyfills

We will write our own versions of several common string methods. For each one, I’ll explain the logic step by step and then show the code. Remember, these are simplified educational versions that aim to mimic the core behavior as per the MDN specification.

Polyfill for includes

The includes method determines whether one string can be found within another string, returning true or false. It takes a search string and an optional position from which to start searching. It is case-sensitive.

Logic: Loop through the main string from the given start position. For each index, check if the substring starting there matches the search string. If we find a full match, return true. If we reach the end without a match, return false.

if (!String.prototype.myIncludes) {
  String.prototype.myIncludes = function(search, start) {
    if (search instanceof RegExp) {
      throw new TypeError("First argument must not be a RegExp");
    }
    start = start || 0;
    if (start + search.length > this.length) return false;
    return this.indexOf(search, start) !== -1;
  };
}

Wait, we used indexOf above! That’s cheating if we want a from-scratch polyfill without relying on other ES6 methods. So let’s do a straight loop:

String.prototype.myIncludes = function(search, start) {
  if (search instanceof RegExp) throw new TypeError("First argument must not be a RegExp");
  start = start || 0;
  var source = this;
  while (start + search.length <= source.length) {
    var match = true;
    for (var i = 0; i < search.length; i++) {
      if (source[start + i] !== search[i]) {
        match = false;
        break;
      }
    }
    if (match) return true;
    start++;
  }
  return false;
};

Now we have a pure implementation. The nested loop checks for character-by-character equality at each possible starting position.

Polyfill for startsWith

startsWith checks if a string begins with the characters of a specified string, returning true or false. It also accepts an optional position.

Logic: From the given position, compare the characters of the source string with the search string. If all match, return true. If any mismatch or if the remaining length is shorter than the search string, return false.

String.prototype.myStartsWith = function(searchString, position) {
  position = position || 0;
  if (position + searchString.length > this.length) return false;
  for (var i = 0; i < searchString.length; i++) {
    if (this[position + i] !== searchString[i]) {
      return false;
    }
  }
  return true;
};

This is straightforward: we just compare the two strings side by side from the starting index.

Polyfill for endsWith

endsWith checks if a string ends with the characters of a specified string, returning true or false. It accepts an optional length parameter, which sets the length of the string to consider. If not provided, it defaults to the full string length.

Logic: We need to compare the end of the source string (up to the given length) with the search string. The start index for comparison will be (length - searchString.length).

String.prototype.myEndsWith = function(searchString, length) {
  var sourceLen = length !== undefined ? length : this.length;
  if (sourceLen > this.length) sourceLen = this.length;
  var startIndex = sourceLen - searchString.length;
  if (startIndex < 0) return false;
  for (var i = 0; i < searchString.length; i++) {
    if (this[startIndex + i] !== searchString[i]) {
      return false;
    }
  }
  return true;
};

Test it:

console.log("Hello world".myEndsWith("world")); // true
console.log("Hello world".myEndsWith("Hello", 5)); // true

Polyfill for repeat

repeat constructs and returns a new string which contains the specified number of copies of the string on which it was called, concatenated together. It throws a RangeError if the count is negative or Infinity, and a count of zero returns an empty string.

Logic: We start with an empty string, then loop count times, appending the original string each time. However, for large counts, this would be inefficient. For a polyfill, a simple loop is fine as it’s unlikely to be used with huge numbers in older browsers. But we can implement a more efficient method using doubling.

For simplicity, we’ll do a loop and also handle non-integer counts by flooring them.

String.prototype.myRepeat = function(count) {
  if (count < 0 || count === Infinity) {
    throw new RangeError("Invalid count value");
  }
  count = Math.floor(count);
  var result = "";
  for (var i = 0; i < count; i++) {
    result += this;
  }
  return result;
};

Test: "abc".myRepeat(3) gives "abcabcabc".

Polyfill for trim

trim removes whitespace from both ends of a string. Whitespace includes spaces, tabs, no-break spaces, and all line terminator characters.

Logic: Use a regular expression to strip leading and trailing whitespace. Alternatively, loop from the start and end to find the first non-whitespace character and slice the string.

A regex approach is simple and works in older environments:

String.prototype.myTrim = function() {
  return this.replace(/^[suFEFFxA0]+|[suFEFFxA0]+$/g, "");
};

s matches spaces, tabs, line breaks; uFEFF is BOM (Byte Order Mark); xA0 is non-breaking space. This closely matches the ES5 specification.

Common interview string problems and their logic

Beyond polyfills, interviews often test your ability to manipulate strings using basic algorithms. Here are a few classic problems, along with the thought process and solutions.

1. Reverse a string

Probably the most famous beginner question: “Write a function that reverses a string.” The trick is to avoid using the built-in reverse method on arrays, at least in the explanation, but we can show multiple approaches.

Approach 1: Loop from end to start

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

Approach 2: Using array methods (built-in but still asked)

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

2. Check if a string is a palindrome

A palindrome reads the same forward and backward. You can use the reverse function and compare, but it’s more efficient to compare characters from both ends moving inward.

function isPalindrome(str) {
  str = str.toLowerCase().replace(/[^a-z0-9]/g, ""); // sanitize
  var left = 0;
  var right = str.length - 1;
  while (left < right) {
    if (str[left] !== str[right]) return false;
    left++;
    right--;
  }
  return true;
}

3. Count occurrences of a character or substring

A straightforward loop or using split:

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

// Using split trick (but not recommended for interviews if they want algorithm)
function countSubstring(str, sub) {
  return str.split(sub).length - 1;
}

4. Truncate a string

Write a function that truncates a string to a given length and appends “…” if it was truncated.

function truncate(str, maxLength) {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength) + "...";
}

5. Capitalize the first letter of each word

A classic transformation:

function capitalizeWords(str) {
  return str.split(" ").map(function(word) {
    if (word.length === 0) return word;
    return word[0].toUpperCase() + word.slice(1).toLowerCase();
  }).join(" ");
}

6. Remove duplicates from a string

We can use a Set, but interviewers might ask for a manual approach:

function removeDuplicates(str) {
  var seen = {};
  var result = "";
  for (var i = 0; i < str.length; i++) {
    if (!seen[str[i]]) {
      seen[str[i]] = true;
      result += str[i];
    }
  }
  return result;
}

These exercises train you to think in loops and conditionals, which is exactly the skill needed to write polyfills or solve algorithmic challenges.

The importance of understanding built-in behavior

When you write a polyfill, you are essentially stepping into the shoes of the JavaScript engine. You learn to handle edge cases: what happens if the argument is a RegExp? What if the count is Infinity? How does case-sensitivity work? This depth of understanding makes you a safer and more precise developer.

In an interview, if you can not only use includes but also explain how it might be implemented and then code it up on a whiteboard, you demonstrate a fundamental command of the language that many candidates lack. It shows you don’t just memorize methods; you understand principles.

Moreover, knowing the internal logic helps you debug mysterious bugs. For instance, understanding that trim removes a specific set of whitespace characters, not just spaces, prevents unexpected failures when dealing with user input.

Visualizing string processing flow

I often picture a string method as a loop with a pointer scanning across the characters. For includes, imagine a sliding window. You have the main string, and you slide the search string along it, checking each position until you find a match or reach the end.

For startsWith, you only look at the very beginning, like checking the first few letters of a book title to see if it matches a given prefix. For endsWith, you look at the last letters, moving your attention to the tail of the string.

For something like repeat, it’s like a factory that takes a template and stamps out copies, joining them one after another. For trim, you walk in from both edges, trimming away whitespace until you hit a visible character, then capture the inner part.

This mental imagery makes writing polyfills much easier because you translate the visual into code.

Visulalization

Conclusion

Today we’ve gone beyond just using string methods and dug into the logic that powers them. We built polyfills for includes, startsWith, endsWith, repeat, and trim, seeing how simple loops and conditionals can replicate browser-native behavior. We also solved common interview string problems to reinforce the thinking pattern.

Let’s summarize the key takeaways:

  • String methods are built-in tools that manipulate strings, but they are all based on fundamental operations like looping, comparison, and slicing.
  • Polyfills are implementations of modern methods that enable them in older environments, and writing them deepens your understanding of JavaScript.
  • We created step-by-step polyfills for includes, startsWith, endsWith, repeat, and trim, each with a clear logic.
  • Interview problems like reversing a string, checking palindromes, counting occurrences, and capitalizing words rely on the same foundational skills.
  • Truly comprehending built-in behavior prepares you for technical interviews and makes you a more effective developer.

The next time you use a string method, you’ll know exactly what’s happening behind the scenes, and you’ll be ready to tackle any string-related challenge that comes your way.

Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on LinkedIn and X, where I post more about web development.

What is dogfooding? How JetBrains builds better developer tools

Dogfooding in software development means using your own products to build, test, and improve them. At JetBrains, it’s a core part of how we create developer tools like IntelliJ IDEA, YouTrack, and Rider.

We don’t rely on assumptions or abstract user personas. We use our tools every day in real workflows, which keeps us close to the problems developers actually face.

Our CEO, Kirill Skyrgan, puts it:

“You can only build truly great software if you use it yourself. Every feature and every decision comes from firsthand experience.”

What is dogfooding in software development?

Dogfooding — short for “eating your own dog food” — means putting your product through the same real-world use as your customers.

Our engineers, designers, product managers, and even technical writers build their daily workflows around JetBrains tools. We write code in  IntelliJ IDEA and track issues and internal project statuses in YouTrack.

It’s not about internal compliance – no one forces anyone to use a product. It’s about trust. We use our tools because they help us do our jobs better, and when they don’t, we fix them.

This direct connection between building and using keeps us grounded. We don’t chase trends or design for hypothetical users. If something slows us down, we know it likely affects thousands of developers too

Benefits of dogfooding: Faster feedback and better software

Dogfooding gives us what every product company dreams of: immediate, unfiltered feedback.

Instead of waiting weeks for customer reports, our developers spot issues as they code.
When a feature feels unintuitive or a shortcut doesn’t work as expected, the fix often starts that same day or even the same hour.

This tight feedback loop turns every JetBrainer into a quality advocate. It shortens the distance between problem and solution, helping us catch things long before they ever reach users.It also fosters empathy. Using the tools ourselves means we understand not only what users say, but what they experience. We feel the slowdowns, the friction points, and the “why is this like that?” moments – and we care enough to address them.

“Those thousands of tiny corrections made over time are what turn a good product into a great one,” Kirill shared. “They come from people who use the tool every day and want it to be better, not for KPIs, but because they genuinely care.”

Examples of dogfooding at JetBrains

Dogfooding shapes every JetBrains product, often long before release.

Rider: From unstable to production-ready

One of the best examples of dogfooding in action is Rider, our .NET IDE. Back in 2016, when it was still unstable and full of rough edges, JetBrains developers began using it for their work long before it was officially released. Some days, you couldn’t even type because the editor would crash. But instead of giving up, teams fixed the issues they encountered on the spot.

That perseverance turned Rider from an experiment into a world-class IDE. The same principle has shaped countless JetBrains products since.

YouTrack: Built and managed in itself

Another case is the YouTrack team, who use their own issue tracker to manage every internal project and improvement flows for the product itself. That constant internal use surfaces edge cases and drives continuous refinement.

Junie: Shaped before users ever saw it

Junie, one of our newer tools, was used internally months before its closed beta.

The team started using Junie internally in December 2024, even before it reached closed Beta. From the very beginning, internal feedback played a major role in shaping how the product evolved. Team members quickly identified things that didn’t feel quite right, from small interface quirks to moments where Junie didn’t respond as expected. This early insight helped the team refine the experience long before anyone outside JetBrains ever saw it.

One particularly important piece of feedback was that Junie didn’t explain enough about what it was doing. That lack of clarity made some interactions feel confusing. Because the team experienced this themselves, they were able to rethink the product’s communication early on and make it more transparent and helpful.

Another area that benefited enormously from dogfooding was Junie’s connection with different work environments used throughout the company. JetBrainers rely on a wide variety of setups in their daily work, and using Junie across these revealed many edge cases the team wouldn’t have spotted otherwise. Each of these discoveries turned into improvements – hundreds of them.

How dogfooding improves developer experience and ownership

Dogfooding doesn’t just improve products — it changes how teams work. When you use what you build, the distinction between “developer” and “user” disappears. There’s no handoff, no abstraction.

That perspective creates stronger ownership. Decisions have immediate, visible impact. Teams see the results of their work in real time.

Dogfooding AI tools at JetBrains

Our teams use AI-assisted features internally long before release, testing what feels useful, what feels distracting, and what actually improves productivity.

This helps us avoid building AI for the sake of trends. We build it because we need it — and we refine it until it works in real development environments.

Why dogfooding matters for building better software

Dogfooding is how we make sure our tools meet the same high standards our users expect. It keeps us honest, motivated, and connected to the work we do. It’s not always comfortable – finding bugs in your own product rarely is – but it’s the most authentic way we know to build software that truly makes a difference.

This is what has kept JetBrains thriving for over two decades: a culture of doers who build, test, and improve from the inside.

As one of our technical leads put it:

“If I start any new project, the first milestone for it is definitely dogfooding. It’s one of the most important quality gates for the product and a crucial source of high-quality feedback.”

Build what you believe in

Dogfooding isn’t just a process we follow – it’s a fundamental part of how we work. It helps us stay close to our mission, keep improving, and make sure that when developers everywhere open a JetBrains tool, it feels like it was built by someone who truly understands them.

Because it was.

If this way of working resonates with you, if you care about the craft, and prefer solving real problems over just chasing trends — you’ll likely feel at home here. Check out out careers page for open roles!