Updated on
The fastest way to count how many times a character appears in a string in C# is source.AsSpan().Count(toFind), a single call that allocates nothing. The LINQ way is source.Count(c => c == toFind), which reads better and costs an enumerator and a delegate.
Every other approach on this page counts the same thing by a different route: a loop, IndexOf(), Split(), Replace() or a regular expression. They differ in speed and allocation, not in the answer, and the benchmark at the end of the article puts numbers on the difference.
Using LINQ Count()
We can use System.Linq to count the number of characters using the Count() method.
Let’s create a method to look at the usage of Count:
public int CountCharsUsingLinqCount(string source, char toFind)
{
return source.Count(t => t == toFind);
}
The Count() method of Linq iterates over every character of the string and executes the predicate, in our case t => t == toFind for each of them.
On executing the method, we get the number of occurrences of the character toFind in the string main:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingLinqCount(main, toFind); Assert.Equal(2, actual);
Using foreach Loop
A simple approach to iterating over all the characters of the given string is to use the foreach loop. This allows us to keep a count of the occurrence of any character.
Let’s create a method to count how many times a character appears in the string using foreach:
public int CountCharsUsingForeach(string source, char toFind)
{
int count = 0;
foreach (var ch in source)
{
if (ch == toFind)
count++;
}
return count;
}
We keep a counter variable count and keep increasing its value every time we encounter the desired character in the string.
Thus, this method returns the number of occurrences of toFind in the string main:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingForeach(main, toFind); Assert.Equal(2, actual);
Using foreach Loop With Span
We can also make use of Span<T> inside a foreach loop to count the character occurrences. The AsSpan() extension method returns a ReadOnlySpan<char> over a string. A ReadOnlySpan<char> is a view over memory the string already owns: AsSpan() copies nothing and allocates nothing. What that buys is slicing without allocating. It does not make a hand-written foreach any faster: on .NET 10 the span loop and the string loop measure the same.
Here, the only change from the previous approach is that we apply the AsSpan() method on the source string:
public int CountCharsUsingForeachSpan(string source, char toFind)
{
int count = 0;
foreach (var c in source.AsSpan())
{
if (c == toFind)
count++;
}
return count;
}
The method returns how many times toFind occurred in the string variable main:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingForeachSpan(main, toFind); Assert.Equal(2, actual);
Using For Loop
Another approach to iterating over a string is to use for loop. This works in a similar manner to foreach where we count the number of times a character occurs in the source string:
public int CountCharsUsingFor(string source, char toFind)
{
int count = 0;
for (int n = 0; n < source.Length; n++)
{
if (source[n] == toFind)
count++;
}
return count;
}
We can also use a char[] instead of a string, but it brings no performance advantage. The call to the ToCharArray() method allocates a new array and copies the whole string into it.
This method returns the count of the character toFind in the string main on execution:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingFor(main, toFind); Assert.Equal(2, actual);
Using IndexOf() Method
A way to count the occurrence of a character within a string is using the IndexOf() method of the string class.
We keep a counter variable and increment it every time the statement source.IndexOf(toFind, n) + 1 returns a value greater than 0. i.e. the character exists in the string:
public int CountCharsUsingIndex(string source, char toFind)
{
int count = 0;
int n = 0;
while ((n = source.IndexOf(toFind, n) + 1) != 0)
count++;
return count;
}
Because IndexOf() returns the match position and the expression adds one, n already points at the character after the match, so the next search starts in the right place and two adjacent occurrences are both counted.
Similar to the previous methods, on executing the method we get the number of occurrences of the character toFind in the string main:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingIndex(main, toFind); Assert.Equal(2, actual);
Using Split() Method
We can count the number of characters using the Split() method:
public int CountCharsUsingSplit(string source, char toFind)
{
return source.Split(toFind).Length - 1;
}
In this example, we split the main string using the character as a delimiter. This results in an array of strings whose length is 1 more than the substring occurrence.
We can execute this method to find the occurrence of the character:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingSplit(main, toFind); Assert.Equal(2, actual);
Using String Replace() Method
Let’s create a method to count the number of occurrences of a character using the Replace() method:
public int CountCharsUsingReplace(string source, char toFind)
{
return source.Length - source.Replace(toFind.ToString(), "").Length;
}
Here, we convert the character to a string and replace it with an empty string. We then find the difference between the length of the original string and the resulting string.
On executing the method, we get the number of occurrences of a character:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingReplace(main, toFind); Assert.Equal(2, actual);
Using Regex Pattern Matching
We can also use Regex pattern matching to count the number of occurrences of a character.
Let’s create a method to do so:
public int CountCharsUsingRegex(string source, char toFind)
{
return Regex.Count(source, Regex.Escape(toFind.ToString()));
}
The static Regex.Count() method arrived in .NET 7 and counts the matches without us building a Regex object on every call. Regex.Escape() stays, because it is what makes the method work for characters such as . and * as well as for letters.
We can then execute this method to count the number of a character within a string:
string main = "Mary Had A Little Lamb"; char toFind = 'L'; int actual = _countChars.CountCharsUsingRegex(main, toFind); Assert.Equal(2, actual);
Using Span Count()
Count() is part of the base class library. MemoryExtensions.Count() extends ReadOnlySpan<T> and Span<T>, so AsSpan() followed by Count() needs no package reference at all:
public int CountCharsUsingSpanCount(string source, char toFind)
{
return source.AsSpan().Count(toFind);
}
Microsoft’s documentation for MemoryExtensions.Count describes the overload we are calling here:
Counts the number of times the specified value occurs in the span.
This is the fastest approach in the benchmark below, and it is the one line to remember from this article.
This method arrived in .NET 8. On .NET 7 and earlier the same extension came from the CommunityToolkit.HighPerformance package, which is where this article first got it, and that package is still the way to reach it on older target frameworks.
We have to give thanks to our reader Joel for mentioning this in the comment section.
Does C# Have a String.Count() Method?
No. System.String exposes Length, not Count, and every Count() call on this page arrives from somewhere else.
Two extension methods supply it, and they are not the same method. Enumerable.Count() lives in System.Linq and applies because a string is an IEnumerable<char>, so it walks the string through an enumerator and tests each character against a predicate.
MemoryExtensions.Count() lives in System and applies to the ReadOnlySpan<char> that AsSpan() returns, so it searches memory for one value with no predicate and no enumerator.
The compiler settles which is which. In a project with ImplicitUsings turned off, drop using System.Linq from a file and source.Count(c => c == toFind) stops compiling, with error CS0411: The type arguments for method 'MemoryExtensions.Count<T>(Span<T>, T)' cannot be inferred from the usage.
So a search for string.Count is really two questions. If the match is a condition, that is LINQ. If one known character is being counted, that is the span overload.
How Do We Count Occurrences of a Substring in C#?
Count() on a span takes a second span, so counting a word costs the same call as counting a character: source.AsSpan().Count("la") returns how many times la appears.
Counting is non-overlapping, which is the detail worth knowing before trusting the number. "aaaa".AsSpan().Count("aa") returns 2, not 3, because the search resumes after each match rather than one position later. Split() and Regex.Count() agree with it and return 2 as well.
Overlapping matches need the loop instead. Call IndexOf() with StringComparison.Ordinal, count the hit, then advance the start position by one rather than by the length of the match.
That StringComparison argument is not decoration. The string.IndexOf(string) overload compares using the current culture by default, so the same code can return a different count on a different machine, while the char overloads and the span overloads are ordinal unless we pass them a culture-sensitive StringComparison.
Let’s write both readings as a pair of methods:
public int CountSubstringNonOverlapping(string source, string toFind)
{
return source.AsSpan().Count(toFind);
}
public int CountSubstringOverlapping(string source, string toFind)
{
var count = 0;
for (var n = 0; (n = source.IndexOf(toFind, n, StringComparison.Ordinal)) != -1; n++)
count++;
return count;
}
Counting how many times a substring occurs is a different question from asking find every position a substring appears at, and if the pieces between the matches are wanted as well as the total, the Split() overloads and StringSplitOptions are the better tool.
How Do We Count Every Character in a String at Once?
One pass and a dictionary answers for every character at once, instead of running one of the methods above once per character.
LINQ writes it in one line. source.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count()) groups the string by character and stores each group’s size, so looking up 'l' in the result reads back how many times it appeared.
A plain loop avoids the grouping allocations and reads just as well. Walk the string once and write counted[c] = counted.GetValueOrDefault(c) + 1, which inserts the key the first time it is seen and increments it afterwards.
Both are case sensitive, so A and a are separate keys. Fold them with char.ToLowerInvariant(c) before the grouping when that is not wanted.
Neither gives a ranking. A Dictionary<char, int> has no order, so the most frequent character comes from ordering the dictionary by value afterwards.
Both forms, side by side:
public Dictionary<char, int> CountEveryCharUsingLinq(string source)
{
return source.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count());
}
public Dictionary<char, int> CountEveryCharUsingLoop(string source)
{
var counted = new Dictionary<char, int>();
foreach (var c in source)
counted[c] = counted.GetValueOrDefault(c) + 1;
return counted;
}
Both forms return a Dictionary<TKey, int>. When the job is narrower than every character and wider than one, such as counting a whole set of characters, such as every vowel, a single pass with a test inside it does the job without building a dictionary at all.
Performance Comparison
Now that we have looked at various methods to count the occurrences of characters within a string, let’s see how they perform against each other.
We’ll be using BenchmarkDotNet to run the performance benchmarks.
First, let’s create a method GenerateStringWithCharArgs():
public IEnumerable<object[]> GenerateStringWithCharArgs()
{
yield return new object[] { "Mary had a little lamb", 'l' };
}
This method will help us run performance tests on all the methods mentioned above that count the occurrences of a character in a string.
To performance test the methods with BenchmarkDotNet, we must mark the methods using the attributes Benchmark and ArgumentsSource:
[Benchmark] [ArgumentsSource(nameof(GenerateStringWithCharArgs))] public int CountCharsUsingLinqCount(string source, char toFind)
The attribute ArgumentsSource takes the name of the public method that is going to provide the values.
Let’s assess the performance results:
| Method | source | toFind | Mean | Error | StdDev | Gen0 | Allocated | |----------------------------------- |--------------------- |------- |-----------:|----------:|----------:|-------:|----------:| | CountCharsUsingSpanCount | Mary (...) lamb [22] | l | 2.781 ns | 0.0832 ns | 0.0779 ns | - | - | | CountCharsUsingForReverseIteration | Mary (...) lamb [22] | l | 10.171 ns | 0.1138 ns | 0.1064 ns | - | - | | CountCharsUsingFor | Mary (...) lamb [22] | l | 10.713 ns | 0.1961 ns | 0.1834 ns | - | - | | CountCharsUsingForeachSpan | Mary (...) lamb [22] | l | 10.817 ns | 0.2450 ns | 0.3886 ns | - | - | | CountCharsUsingForeach | Mary (...) lamb [22] | l | 11.132 ns | 0.2502 ns | 0.2089 ns | - | - | | CountCharsUsingLinqCount | Mary (...) lamb [22] | l | 18.059 ns | 0.3919 ns | 0.6101 ns | 0.0029 | 24 B | | CountCharsUsingIndex | Mary (...) lamb [22] | l | 20.087 ns | 0.1714 ns | 0.1519 ns | - | - | | CountCharsUsingForWithSpan | Mary (...) lamb [22] | l | 20.277 ns | 0.3121 ns | 0.2766 ns | - | - | | CountCharsUsingSplit | Mary (...) lamb [22] | l | 58.498 ns | 1.2234 ns | 2.6595 ns | 0.0238 | 200 B | | CountCharsUsingReplace | Mary (...) lamb [22] | l | 64.534 ns | 1.2493 ns | 2.7159 ns | 0.0105 | 88 B | | CountCharsUsingRegex | Mary (...) lamb [22] | l | 185.276 ns | 2.1502 ns | 1.9061 ns | 0.0029 | 24 B |
There are a few more methods in the benchmark result, and if you want, you can inspect their implementation in the source code.
We can see that CountCharsUsingSpanCount is the fastest approach by a clear margin, because MemoryExtensions.Count() tests a whole vector of characters per step instead of one character at a time. The hand-written loops on this page come next and are close to each other, whether they walk the string or the Span<char>. Also, we see these iteration methods, alongside the Index method, allocate no memory.
On the other hand, Regex pattern matching is the slowest solution to find the number of occurrences of a character in a string, and Split() and Replace() are the two that allocate the most.
Conclusion
In this article, we learned about counting the number of occurrences of a character within a string. We looked at different ways to do so using LINQ, iterating over the string, and other in-built methods.
The table below lists every approach, whether it allocates, and when to reach for it:
| Approach | Code | Allocates | Reach for it when |
|---|---|---|---|
Span Count() | source.AsSpan().Count(toFind) | No | Always, unless the count has a condition |
for / foreach loop | if (c == toFind) count++; | No | The loop already exists and does other work too |
IndexOf() | while ((n = source.IndexOf(toFind, n) + 1) != 0) | No | The positions matter, not only the total |
LINQ Count() | source.Count(t => t == toFind) | Yes | The match is a predicate, not one character |
Replace() | source.Length - source.Replace(toFind.ToString(), "").Length | Yes | Never, on this job |
Split() | source.Split(toFind).Length - 1 | Yes | The pieces are wanted as well as the count |
Regex.Count() | Regex.Count(source, Regex.Escape(toFind.ToString())) | Yes | The thing being counted is a pattern |
If there is one line to take away, it is source.AsSpan().Count(toFind) for a known character, and source.Count(c => c == toFind) when the match is a condition rather than a single character.
Tested with .NET 10.0.10 and BenchmarkDotNet 0.15.8.

There’s one option you’re missing: SpanExtensions.Count from the CommunityToolkit.HighPerformance nuget package (previously known as Microsoft.Toolkit.HighPerformance.
SpanExtensions.Count<T>(Span<T>, T) Method (Microsoft.Toolkit.HighPerformance.Extensions) | Microsoft Learn
This method uses CPU hardware intrinsics like SSE2 and AVX to speed things up, and on my machine it beats all the other approaches by a wide margin. On my machine:
CountCharsUsingSpanCount: 4.732 ns
CountCharsUsingFor: 15.393 ns
So the CommunityToolkit.HighPerformance version is more than 3 times faster than the fastest option in this article.
@James Curran – for LINQ Count() there’s more than one culprit. One of them is the lambda function, but the other is virtual method calls for GetEnumerator/MoveNext/Current in any IEnumerable<T> based code. I’m afraid that inlining the lambda would still not make the LINQ version the fastest. Also I’d argue that using hardware intrinsics is more “native” than LINQ could ever be, but I guess that depends on what you mean by “native”.
Hi Joel. Thanks a lot for the comment. To be honest, I personally didn’t know about the package, so this is truly great info. I couldn’t get a similar result, in terms of such a big difference in speed, on my machine, but still, it is the fastest way using the ReadOnlySpan.Count extension method. So, we will definitely update the article to include this valuable info.
I meant “native” is the sense of a (seemingly) instance method whose specific function is what we want to do. It would be natural to assume that is the best way to do it. (And it might be a good idea to override Enumerable.Count() for strings with an extension method calling one of the better methods)
Also, I’m not convinced that GetEnumerator/MoveNext/Current is the problem. If so, CountCharsUsingFor would beat CountCharsUsingForeach by a wider margin. The only real difference between CountCharsUsingForeach and CountCharsUsingLinqCount is the lambda.
GetEnumerator/MoveNext/Current is certainly not the entire issue, but it’s definitely part of it. I think you’ll find that the compiler specializes foreach for certain types like arrays and strings, specifically to avoid the overhead of allocating an enumerator and making two virtual method calls per iteration. This is why the LINQ method allocates memory in the benchmark, but the foreach method does not.
You could verify this by making a version of CountCharsUsingForeach that manually calls GetEnumerator/MoveNext/Current and compare it to the performance of the foreach version.
This optimization is specific to the foreach statement, and isn’t possible in LINQ extension methods, unfortunately.
It’s really a shame that the most “native” way to do it (LINQ Count()) comes out so badly. The culprit is the lambda function, which adds a call/return to each iteration of the loop. The actual comparison is very fast, but pushing the parameters and calling the function is many times slower. If MSFT would come up with a JITer that inlined lambdas, it would be an incredible production boon for .NET coding.
Thanks a lot, James for the comment here. You can see some additional notes for you in this comment: https://code-maze.com/counting-char-within-string-csharp/#comment-6676