Explain

How can I count the number of times a particular string occurs in another string?

There are multiple ways to count occurrences of a substring within a larger string. Common approaches in JavaScript include:

  1. Looping with indexOf() or search() in a while loop until you no longer find matches.
  2. Regular expressions with match(), matchAll() (ES2020+), or a repeated exec() loop to find multiple occurrences.

Below are a few detailed examples and considerations.

1. Looping with indexOf() in JavaScript

function countOccurrences(str, subStr) {
  let count = 0;
  let position = 0;

  while (true) {
    // Find subStr starting from position
    const foundPos = str.indexOf(subStr, position);
    if (foundPos === -1) {
      break; // no more matches
    }
    count++;
    // Move position past the last found index
    position = foundPos + subStr.length;
  }

  return count;
}

console.log(countOccurrences("ababab", "ab")); // 3
console.log(countOccurrences("hello world", "o")); // 2
  • indexOf(subStr, fromIndex) returns the index where subStr is found (or -1 if none).
  • We increment count each time, then move position forward past the substring to find subsequent matches.
  • This approach finds non-overlapping occurrences.

Overlapping Occurrences?

  • If you need to count overlapping occurrences (e.g., "aaaa" has 3 overlapping occurrences of "aa"), you’d shift position by 1 instead of subStr.length:
    position = foundPos + 1;
    

2. Using a Regular Expression

2.1 String.prototype.match() (Non-Overlapping)

function countOccurrencesRegex(str, subStr) {
  // Escape any special regex characters in subStr if needed:
  const escapedSubStr = subStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

  // Create a "global" RegExp
  const pattern = new RegExp(escapedSubStr, 'g');
  // str.match(pattern) returns an array of matches, or null if none
  const matches = str.match(pattern);
  return matches ? matches.length : 0;
}

console.log(countOccurrencesRegex("ababab", "ab")); // 3
console.log(countOccurrencesRegex("hello world", "o")); // 2
  • This also doesn’t handle overlapping matches by default because a global regex proceeds past the matched substring.

2.2 Counting Overlapping Occurrences with RegExp.prototype.exec()

Overlapping can be handled by manually iterating:

function countOverlapping(str, subStr) {
  // Escape if subStr might have special regex characters
  const escapedSubStr = subStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

  // 'g' for multiple matches; we manually manage lastIndex for overlaps
  const pattern = new RegExp(escapedSubStr, 'g');
  let count = 0;
  let match;

  while ((match = pattern.exec(str)) !== null) {
    count++;
    // Manually reset the regex 'lastIndex' to allow overlapping
    pattern.lastIndex = match.index + 1;
  }

  return count;
}

console.log(countOverlapping("aaaa", "aa")); // 3
  • After each match, we shift pattern.lastIndex by 1 (instead of match.index + subStr.length) so the next search can catch overlapping occurrences.

Recommended Courses

2.3 ES2020+ matchAll()

Modern JavaScript also has String.prototype.matchAll(), which returns an iterator of match objects (including capturing groups). You can loop over them and count. However, handling overlaps still requires a workaround similar to the exec() approach.

function countMatchesWithMatchAll(str, subStr) {
  const escapedSubStr = subStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const pattern = new RegExp(escapedSubStr, 'g');
  // matchAll returns an iterator of match results
  return [...str.matchAll(pattern)].length;
}

console.log(countMatchesWithMatchAll("ababab", "ab")); // 3

Still non-overlapping by default—overlapping requires manual resets, which typically means you go back to a custom loop.

3. Other Languages or Approaches

  • Many languages (e.g., Python) have similar methods or libraries to count substrings.
  • If you just need to see if a substring occurs at all, String.includes() or indexOf(subStr) !== -1 suffices (in JavaScript).
  • For large or complex tasks (like searching big logs or entire files), more advanced or memory-efficient solutions may be needed.

Best Practices

  1. Escape your Substring if it might contain special regex characters (like *, +, ., etc.). Otherwise, your pattern can break or match unintended strings.
  2. Overlapping vs Non-Overlapping: Clarify whether you need overlapping matches. The logic or method changes slightly.
  3. Performance: For very large strings or repetitive tasks, consider efficiency. But for typical use cases, these examples are sufficient.

Final Thoughts

To count occurrences of a substring in JavaScript (or any language):

  • Use a loop with indexOf() or search(), adjusting the start index each time a match is found.
  • Or use regex (match(), exec(), matchAll()) with a global flag.
  • For overlapping occurrences, you must manually adjust how far the search index advances.

Bonus: Level Up Your JavaScript & Coding Interview Skills

If you’re exploring string manipulation and want to sharpen your JavaScript fundamentals or coding interview capabilities, check out these DesignGurus.io courses:

  1. Grokking JavaScript Fundamentals
    Deepen your understanding of closures, prototypes, async/await, and more.

  2. Grokking the Coding Interview: Patterns for Coding Questions
    Master pattern-based problem-solving—useful for both interview prep and real-world dev tasks.

For hands-on feedback, explore Mock Interviews (Coding Mock Interview or System Design Mock Interview) with ex-FAANG engineers. Also, visit the DesignGurus.io YouTube channel for free tutorials on coding patterns, system design, and more.

Summary: Use a while loop with indexOf(), or a regex approach (test(), match(), exec(), matchAll()) to count occurrences. Decide if you need overlapping matches and adjust your strategy accordingly.