Question
In C#, what is the best way to generate a string made of repeated tab characters?
I am learning C# and experimenting with different ways to express the same idea. I want a method Tabs(uint numTabs) that returns a string containing numTabs tab characters.
For example:
Tabs(3) // returns "\t\t\t"
Which of the following implementations is best?
I understand that the answer depends on what “best” means. For example:
- The LINQ version is short, but does calling
RepeatandAggregateadd unnecessary overhead? - The
StringBuilderversion is very clear, but isStringBuilderslower in this case? - The plain string version is simple and easy to understand.
- Or does it not matter much at all?
These questions are mainly to help me build intuition about how C# works.
private string Tabs(uint numTabs)
{
IEnumerable<string> tabs = Enumerable.Repeat("\t", (int)numTabs);
return (numTabs > 0) ? tabs.Aggregate((sum, next) => sum + next) : "";
}
private string Tabs(uint numTabs)
{
StringBuilder sb = new StringBuilder();
for (uint i = 0; i < numTabs; i++)
sb.Append("\t");
return sb.ToString();
}
private string Tabs(uint numTabs)
{
string output = "";
for (uint i = 0; i < numTabs; i++)
{
output += '\t';
}
return output;
}
Short Answer
By the end of this page, you will understand how repeated string creation works in C#, why some approaches are more efficient than others, and which option is usually best for repeating a character such as \t. You will also learn when StringBuilder is useful, why repeated string += can become expensive, and which built-in APIs make this task simplest.
Concept
In C#, string objects are immutable. That means once a string is created, it cannot be changed. If you write code like this:
output += '\t';
a new string is created each time through the loop.
That matters because building a string one piece at a time with += can become inefficient. Each concatenation may copy the old contents into a new string, then add the new character.
For a small number of tabs, this usually does not matter much. But for larger counts, it can become much slower and use more memory.
Why StringBuilder exists
StringBuilder is designed for cases where you build a string gradually. Instead of creating a brand-new string for every append, it keeps a mutable internal buffer and grows it as needed.
That makes it a better choice than repeated += when:
- you are appending inside a loop
- you do not know the final string up front
- the result may be large
Why the LINQ version is not ideal here
LINQ is great for querying collections, but this problem is not really a collection-query problem. Using:
Enumerable.Repeat- then
Aggregate
adds extra abstraction and overhead for something very simple. Also, Aggregate((sum, next) => sum + next) still performs repeated string concatenation, so it does not avoid the core inefficiency.
The simplest built-in solution
For repeating a single character in C#, the best built-in tool is usually the string constructor:
new string('\t', count)
This directly creates a string containing the character repeated count times. It is simple, readable, and efficient.
What usually counts as “best”
In real code, “best” often means some combination of:
- clear to read
- correct
- efficient enough
- uses the right tool for the job
For this specific task, new string('\t', count) is usually the best answer.
If you were building a more complex string piece by piece, StringBuilder would often be the better choice.
Mental Model
Think of a string like text carved into stone.
- If you want to add one more character, you cannot change the stone.
- You must carve a new stone with all the old text plus the new character.
That is what happens with repeated string +=.
Now think of StringBuilder like writing on a whiteboard.
- You can keep adding characters without recreating everything each time.
- When you are done, you copy the final result into a string.
And think of new string('\t', count) like ordering a pre-made rope of exactly the right length.
- You already know the character
- You already know how many times it should repeat
- So the runtime can create the final result directly
That is why it is usually the cleanest option for this case.
Syntax and Examples
If you want to repeat a single character in C#, the most direct syntax is:
string tabs = new string('\t', 3);
Console.WriteLine(tabs.Length); // 3
Recommended approach
private string Tabs(int numTabs)
{
return new string('\t', numTabs);
}
Example:
Console.WriteLine(Tabs(3) == "\t\t\t"); // True
This is usually the best solution because it is:
- short
- readable
- efficient
Using StringBuilder
If you need to build text in multiple steps, use StringBuilder:
using System.Text;
private string Tabs(int numTabs)
{
StringBuilder sb = new StringBuilder(numTabs);
for (int i = 0; i < numTabs; i++)
{
sb.Append('\t');
}
return sb.ToString();
}
This is good when you are constructing more than just repeated tabs.
Using += in a loop
private string Tabs(int numTabs)
{
string output = "";
for (int i = 0; i < numTabs; i++)
{
output += '\t';
}
return output;
}
This works, but it is less efficient for larger values because each += creates a new string.
LINQ version
using System.Linq;
private string Tabs(int numTabs)
{
var tabs = Enumerable.Repeat("\t", numTabs);
return numTabs > 0 ? tabs.Aggregate((sum, next) => sum + next) : "";
}
This is valid, but it is not a great fit for the problem. It is more complex than needed and still relies on repeated concatenation internally.
Step by Step Execution
Consider this example:
string result = new string('\t', 4);
What happens step by step
- The character
\tis chosen as the content to repeat. - The count
4tells C# how many times to repeat it. - The runtime allocates a string of length 4.
- Each position is filled with a tab character.
- The final string is returned.
The result is equivalent to:
"\t\t\t\t"
Compare with +=
string output = "";
for (int i = 0; i < 4; i++)
{
output += '\t';
}
Trace:
- Start:
output = "" - After loop 1:
"\t" - After loop 2:
"\t\t" - After loop 3:
"\t\t\t" - After loop 4:
"\t\t\t\t"
But under the hood, a new string is created on every iteration.
Compare with StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 4; i++)
{
sb.Append('\t');
}
string result = sb.ToString();
Trace:
- Start with an empty internal buffer
- Append tab 4 times into the buffer
- Convert the final buffer to a string once
This avoids creating a new string during every iteration.
Real World Use Cases
Repeating characters or short strings appears in many real programs.
Indentation and formatting
Your tabs example is common when generating:
- formatted console output
- source code
- XML or JSON-like text
- nested debug output
string indent = new string('\t', depth);
Console.WriteLine(indent + "Node");
Padding output
You may repeat spaces or symbols to align text:
string line = new string('-', 20);
Console.WriteLine(line);
Generating separators
string border = new string('=', 40);
Useful for logs, reports, and command-line tools.
Creating test data
When testing string-processing code, developers often generate repeated input:
string sample = new string('A', 1000);
Building text files
When producing templates, markdown, SQL, or code output, developers often combine repeated characters with other text. In that case, StringBuilder is often used for the full output while new string(char, count) handles simple repeated pieces.
Real Codebase Usage
In real projects, developers usually choose the tool based on the shape of the problem.
Common pattern: direct character repetition
If the task is exactly “repeat one character N times”, developers typically use:
string indent = new string('\t', level);
This is common in:
- pretty-printers
- text exporters
- log formatters
- code generators
Common pattern: build a larger string with many parts
When repeated characters are only one part of a larger output, developers often use StringBuilder:
var sb = new StringBuilder();
sb.Append(new string('\t', level));
sb.AppendLine("Item");
Validation and guard clauses
In production code, developers often validate counts before constructing strings:
private string Tabs(int numTabs)
{
if (numTabs < 0)
throw new ArgumentOutOfRangeException(nameof(numTabs));
return new string('\t', numTabs);
}
This is clearer than using uint, because many .NET APIs expect int, including the string(char, int) constructor.
Configuration-driven formatting
Indentation levels often come from settings or parser depth values. In those cases, the repeated string is used as a formatting helper, not as business logic itself.
Early return pattern
Developers sometimes make edge cases explicit:
private string Tabs(int numTabs)
{
if (numTabs == 0)
return string.Empty;
return new string('\t', numTabs);
}
This is optional, since new string('\t', 0) already returns an empty string, but some teams prefer the explicit check for readability.
Common Mistakes
1. Using += inside a loop for large strings
Broken or inefficient pattern:
string output = "";
for (int i = 0; i < 10000; i++)
{
output += '\t';
}
Why it is a problem:
- each concatenation creates a new string
- performance gets worse as the string grows
Better:
string output = new string('\t', 10000);
or, for more complex output:
var sb = new StringBuilder();
2. Using LINQ for simple string repetition
var tabs = Enumerable.Repeat("\t", count);
string result = tabs.Aggregate((a, b) => a + b);
Why it is not ideal:
- more complex than necessary
- still performs repeated concatenation
- less readable for this specific task
Better:
string result = new string('\t', count);
3. Using uint where an int is expected
Your original method uses uint, but many .NET APIs use int.
Example:
new string('\t', (int)numTabs);
This cast is unnecessary if you just accept int in the first place.
Better:
private string Tabs(int numTabs)
{
if (numTabs < 0)
throw new ArgumentOutOfRangeException(nameof(numTabs));
return new string('\t', numTabs);
}
4. Forgetting edge cases
What should happen when the count is 0?
Good news: this works correctly:
new string('\t', 0) // returns ""
5. Mixing character and string types without need
These both work in some contexts, but repeating a single character is best expressed as a char:
'\t'
instead of:
"\t"
Use char when you mean one character.
Comparisons
| Approach | Example | Readability | Performance | Best for |
|---|---|---|---|---|
new string(char, count) | new string('\t', 3) | High | Excellent | Repeating one character |
StringBuilder | sb.Append('\t') in a loop | High | Very good | Building larger strings in steps |
string += in loop | output += '\t'; | Medium | Poor for large counts | Tiny examples or teaching basics |
LINQ Repeat + Aggregate | Enumerable.Repeat(...).Aggregate(...) | Low to medium | Poor here | Rarely the best choice for this task |
new string vs StringBuilder
| Situation | Better choice |
|---|---|
| Repeat one known character N times | new string(char, count) |
| Build a sentence, file, or report piece by piece | StringBuilder |
| Final size known exactly and simple | new string |
| Many appends of mixed text | StringBuilder |
uint vs int
| Type | Notes |
|---|---|
int | Most common for counts and indexes in .NET APIs |
uint | Rarely needed for this kind of method; often causes extra casts |
For typical C# code, int is the more practical choice here.
Cheat Sheet
Best option for repeating one character
string tabs = new string('\t', count);
Recommended method
private string Tabs(int numTabs)
{
if (numTabs < 0)
throw new ArgumentOutOfRangeException(nameof(numTabs));
return new string('\t', numTabs);
}
When to use what
- Use
new string(char, count)for one repeated character - Use
StringBuilderfor many appends or complex text generation - Avoid
string +=in loops for large strings - Avoid LINQ for this simple repetition task
Key rule
string is immutable in C#.
That means:
output += 'x';
creates a new string each time.
Good examples
new string(' ', 4)
new string('-', 20)
new string('\t', 3)
Edge cases
new string('x', 0) // ""
new string('x', -1) // error
Performance summary
- Best here:
new string('\t', count) - Good for complex building:
StringBuilder - Usually worst for large counts:
+=in a loop
FAQ
What is the fastest way to repeat a character in C#?
Usually new string(char, count) is the fastest and clearest way when you need one character repeated many times.
Is StringBuilder faster than string concatenation?
Yes, usually when you append repeatedly in a loop. StringBuilder avoids creating a new string on every append.
Is LINQ a good choice for repeating a string in C#?
Not usually for this problem. It is more verbose internally and less efficient than the direct new string solution.
Why is string += in a loop slow?
Because strings are immutable. Every concatenation creates a new string and copies existing content.
Should I use uint for counts that cannot be negative?
Usually no. Most .NET APIs use int, so using uint often makes code less convenient and requires casts.
What happens if the repeat count is 0?
new string('\t', 0) returns an empty string.
Can StringBuilder still be the right choice here?
Yes, if tabs are only one small part of a larger string you are constructing step by step.
How do I repeat a string instead of a character in C#?
There is no built-in constructor for repeating whole strings. For that, use StringBuilder, string.Concat, or another approach depending on the case.
Mini Project
Description
Create a small formatting helper for console output that generates indentation and section separators. This demonstrates when new string(char, count) is the simplest tool and when it can be combined with other string-building patterns.
Goal
Build a helper that prints a simple tree-like report using repeated tabs and repeated separator characters.
Requirements
- Create a method that returns
ntab characters. - Create a method that returns a separator line made of
-characters. - Print at least three lines with different indentation levels.
- Handle zero correctly so no tabs or separators are added when the count is 0.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.