C#
substring search
string manipulation
coding tutorial
C# programming

Finding all positions of substring in a larger string in C

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In C#, the usual way to find every position of a substring is to call IndexOf in a loop and keep moving the starting index forward. The key detail is deciding whether matches are allowed to overlap, because that changes how far the next search should advance.

The Basic IndexOf Loop

For ordinary non-overlapping matches, the algorithm is straightforward:

csharp
1using System;
2using System.Collections.Generic;
3
4public static class SearchDemo
5{
6    public static List<int> FindAll(string text, string pattern)
7    {
8        if (string.IsNullOrEmpty(pattern))
9            throw new ArgumentException("Pattern must not be empty.", nameof(pattern));
10
11        var positions = new List<int>();
12        int start = 0;
13
14        while (true)
15        {
16            int index = text.IndexOf(pattern, start, StringComparison.Ordinal);
17            if (index == -1)
18                break;
19
20            positions.Add(index);
21            start = index + pattern.Length;
22        }
23
24        return positions;
25    }
26
27    public static void Main()
28    {
29        Console.WriteLine(string.Join(", ", FindAll("abc abc abc", "abc")));
30    }
31}

This prints 0, 4, 8. After each match, the next search begins after the matched substring.

Handling Overlapping Matches

If overlapping matches matter, move the starting position by one character instead of by the pattern length.

csharp
1using System;
2using System.Collections.Generic;
3
4public static class OverlapDemo
5{
6    public static List<int> FindAllOverlapping(string text, string pattern)
7    {
8        if (string.IsNullOrEmpty(pattern))
9            throw new ArgumentException("Pattern must not be empty.", nameof(pattern));
10
11        var positions = new List<int>();
12        int start = 0;
13
14        while (true)
15        {
16            int index = text.IndexOf(pattern, start, StringComparison.Ordinal);
17            if (index == -1)
18                break;
19
20            positions.Add(index);
21            start = index + 1;
22        }
23
24        return positions;
25    }
26
27    public static void Main()
28    {
29        Console.WriteLine(string.Join(", ", FindAllOverlapping("ababa", "aba")));
30    }
31}

This prints 0, 2, which is correct for overlapping occurrences.

Be Explicit About Comparison Rules

IndexOf has overloads that let you choose comparison behavior. That matters because text matching can be case-sensitive, case-insensitive, ordinal, or culture-aware.

For technical text such as identifiers, protocol values, or file formats, StringComparison.Ordinal is usually the safest choice. For case-insensitive technical matching, use StringComparison.OrdinalIgnoreCase.

csharp
int index = text.IndexOf(pattern, start, StringComparison.OrdinalIgnoreCase);

Being explicit avoids surprises and makes the method contract easier to understand.

Returning an Iterator Instead of a List

If you want lazy consumption of the results, yield return is a nice alternative to building a list up front.

csharp
1using System;
2using System.Collections.Generic;
3
4public static class IteratorSearch
5{
6    public static IEnumerable<int> FindAll(string text, string pattern)
7    {
8        if (string.IsNullOrEmpty(pattern))
9            throw new ArgumentException("Pattern must not be empty.", nameof(pattern));
10
11        for (int start = 0; ; start++)
12        {
13            int index = text.IndexOf(pattern, start, StringComparison.Ordinal);
14            if (index == -1)
15                yield break;
16
17            yield return index;
18            start = index + pattern.Length - 1;
19        }
20    }
21}

This is useful when callers want to process matches as they are found instead of storing all positions immediately.

When Regex Is the Wrong Tool

If you are searching for a literal substring, regular expressions are often unnecessary. Regex adds power when the pattern itself is variable or rule-based, but it also adds escaping rules and more complexity.

For example, this is a valid regex search:

csharp
1using System;
2using System.Text.RegularExpressions;
3
4foreach (Match match in Regex.Matches("cat cot cut", "c.t"))
5{
6    Console.WriteLine(match.Index);
7}

But if the goal is simply "find every occurrence of cat," IndexOf is clearer and usually the better tool.

Common Pitfalls

The biggest mistake is forgetting to define whether overlaps count. start = index + pattern.Length and start = index + 1 produce different answers, and both can be correct depending on the requirement.

Another issue is leaving StringComparison implicit. That makes the method behavior less obvious and can cause unexpected casing or culture behavior.

Developers also forget to guard against an empty pattern. Without that check, the loop logic can become ambiguous or even infinite.

Finally, do not overuse regex for simple literal searches. It solves a broader problem and is harder to reason about when plain IndexOf already matches the requirement.

Summary

  • Loop with IndexOf to find all substring positions in C#.
  • Advance by pattern.Length for non-overlapping matches.
  • Advance by 1 for overlapping matches.
  • Specify StringComparison explicitly so the matching rule is clear.
  • Guard against empty patterns before entering the search loop.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.