Substring returns a new string made of part of an existing one. It has two overloads:
s.Substring(startIndex): fromstartIndexto the end.s.Substring(startIndex, length):lengthcharacters, starting atstartIndex.
Indexes start at 0, and the second argument is a length, not an end position. That is the difference from JavaScript's substring(start, end) and Java's substring(begin, end), and the source of many off-by-some bugs when code is ported.
Output:
4271-BLK
SHOE
4271
BLK
0
SHOE-4271-BLK
Like every string method, Substring does not modify the original; it returns a new string, so assign the result.
ArgumentOutOfRangeException and how to avoid it
Substring throws ArgumentOutOfRangeException when the requested range does not fit inside the string: a negative startIndex, a startIndex greater than Length, a negative length, or startIndex + length greater than Length. There is no silent truncation, unlike Python slices.
Output:
Substring(2, 4) -> "ffee"
Substring(2, 5) -> ArgumentOutOfRangeException
Substring(6) -> ""
Substring(7) -> ArgumentOutOfRangeException
Substring(-1) -> ArgumentOutOfRangeException
Substring(0, 10) -> ArgumentOutOfRangeException
The usual guard is to clamp the length to what is left of the string with Math.Min:
Output:
ffee
ea
[]
[]
Calling Substring on a null string throws NullReferenceException, not ArgumentOutOfRangeException, which is why the helper checks for null first.
First and last n characters
Taking a prefix or a suffix is the most common use of Substring. Both throw when the string is shorter than n, so production code checks the length:
Output:
**** 1111
An Unexpecte...
Hi
Hi
When the first part of the string is what you want to drop, Substring(n) alone is enough: "#FF8800".Substring(1) is "FF8800". The related Remove(start, count) does the inverse of Substring: it returns the string with that range taken out.
Substring with IndexOf
Most real substrings are found, not counted. IndexOf returns the position of a character or string (or -1 if it is not there), and LastIndexOf searches from the end. Combine them with Substring to cut text around a separator:
Output:
maya.lopez
example.com
pdf
report.final.v2
WARN
Disk almost full
The arithmetic follows one rule: to take the text between positions a and b (not including either), the start is a + 1 and the length is b - a - 1.
The danger is IndexOf returning -1. email.Substring(0, -1) throws, and worse, email.Substring(-1 + 1) silently returns the whole string. Always check:
Output:
shop.io
(none)
Split as an alternative
When a string is a list of fields with a separator, Split is simpler than a chain of IndexOf and Substring calls, and it has no index arithmetic to get wrong:
Output:
Ana Silva
Porto
maya.lopez
beach.jpg
Split allocates an array plus one string per field, so for a single cut in a hot loop IndexOf and Substring are cheaper. For everything else, readability wins. More options (several separators, removing empty entries, a maximum count) are on string methods. For patterns such as "the digits after ID:", a regular expression is usually the clearest tool.
The range operator (C# 8 and later)
C# 8 added index and range syntax, which works on strings and arrays. ^n means "n from the end" and a..b is the range from a up to but not including b, so the second number is an end index, not a length:
// C# 8 and later
string sku = "SHOE-4271-BLK";
string first4 = sku[..4]; // "SHOE" same as Substring(0, 4)
string middle = sku[5..9]; // "4271" same as Substring(5, 4)
string last3 = sku[^3..]; // "BLK" same as Substring(sku.Length - 3)
string noEnds = sku[1..^1]; // "HOE-4271-BL" drop first and last
char lastChar = sku[^1]; // 'K'
On a string, a range compiles to a Substring call, so it allocates a new string and throws ArgumentOutOfRangeException for the same out-of-bounds cases. Which to use is style; ranges read better when both ends are measured from different sides ([1..^1]).
Substrings without copying: Span
Every Substring call allocates a new string. In performance-sensitive parsing code (reading large files, handling many requests), .NET Core 2.1 and later offer ReadOnlySpan<char>, a view into part of the original string with no copy:
ReadOnlySpan<char> line = "2026-09-24,42.50,EUR".AsSpan();
ReadOnlySpan<char> amount = line.Slice(11, 5); // "42.50", no allocation
decimal value = decimal.Parse(amount, provider: CultureInfo.InvariantCulture);
Spans cannot be stored in fields of ordinary classes or used across await, so they belong in tight, synchronous code. For typical application code, Substring is the right tool.
Common mistakes
- Passing an end index as the length.
s.Substring(2, 5)means five characters from index 2, not "index 2 to 5". To take the text from indexaup to but not including indexb, the length isb - a. - Not checking
IndexOffor -1. Either an exception or, with+ 1, the whole string by accident. - Assuming short strings are long enough. A name, code or line from a file can be shorter than you expect. Clamp with
Math.Minor checkLength. - Discarding the result.
s.Substring(1);on its own line does nothing useful.
Frequently Asked Questions
How does Substring work in C#?
s.Substring(start) returns the characters from index start to the end; s.Substring(start, length) returns length characters beginning at start. Indexes start at 0, and the second argument is a length, not an end index: "Hello".Substring(1, 3) is "ell". The original string is not changed.
Why does Substring throw ArgumentOutOfRangeException?
Because start is negative or greater than the string's length, or start + length goes past the end. The most common cause is a length computed for a longer string, or an IndexOf result of -1 used as a start. Clamp the length with Math.Min(length, s.Length - start) and check IndexOf results before using them.
How do I get the last n characters of a string in C#?
s.Substring(s.Length - n) returns the last n characters, provided n <= s.Length. To be safe for short strings, use s.Length <= n ? s : s.Substring(s.Length - n). In C# 8 and later you can also write s[^n..], which throws the same way when n is too large.
How do I get the substring between two characters in C#?
Find both positions with IndexOf, then take the part between them: int start = s.IndexOf('(') + 1; int end = s.IndexOf(')', start); string inside = s.Substring(start, end - start);. Check that each IndexOf found something (not -1) before calling Substring. For complex patterns, a regular expression is clearer.
Is Substring zero-based in C#?
Yes. The first character is at index 0 and the last at Length - 1. Substring(0, 3) returns the first three characters. Passing Length itself as the start is allowed and returns an empty string.