Airport codes, state abbreviations and 20-character receipt lines all need the start of a string. To get the first N characters of a string in C#, call s.Substring(0, n), or use the range s[..n] on C# 8 and later.
string city = "Sacramento";
string code = city[..3]; // "Sac"
In this tutorial, I’ll line up the options for the C# get first N characters of string question, plus the first and last letter, a safe Left() helper, and output from real .NET 10 runs.
Get the First N Characters of a String with Substring(0, n) or s[..n]
Let’s build airport-style codes from US city names. Substring(0, 3) starts at index 0 and copies three characters, and city[..3] does the same:
string[] cities = { "Sacramento", "Albuquerque", "Savannah", "Tallahassee" };
foreach (string city in cities)
{
string viaSubstring = city.Substring(0, 3);
string viaRange = city[..3]; // C# 8 and later
Console.WriteLine($"{city,-12} {viaSubstring} {viaRange.ToUpperInvariant()}");
}
Output:
Sacramento Sac SAC
Albuquerque Alb ALB
Savannah Sav SAV
Tallahassee Tal TAL
![C# get first N characters of string with Substring(0, 3) and the range operator [..3], city names to airport-style codes in VS Code](https://aspdotnethelp.com/wp-content/uploads/2026/10/csharp-get-first-n-characters-of-string-substring-range.png)
The second argument of Substring is a length, not an end index. In [..3], the end is exclusive, and the compiler turns it into Substring(0, 3), so you don’t pay anything for the nicer syntax.
Both return a new string, since strings are immutable (the key difference between string and StringBuilder). For initials, you’d get the first character of each word instead.
Why Does Substring Throw ArgumentOutOfRangeException?
Ask for five characters from Iowa and both forms fail. Here’s the real exception:
string state = "Iowa";
int n = 5;
try { Console.WriteLine(state.Substring(0, n)); }
catch (ArgumentOutOfRangeException ex) { Console.WriteLine($"Substring: {ex.Message}"); }
try { Console.WriteLine(state[..n]); }
catch (ArgumentOutOfRangeException ex) { Console.WriteLine($"Range: {ex.Message}"); }
Output:
Substring: Index and length must refer to a location within the string. (Parameter 'length')
Range: Index and length must refer to a location within the string. (Parameter 'length')

Substring throws when startIndex + length lands past the end. The range shows the identical message because it calls Substring under the hood.
Safe Version with Math.Min
Clamp n to the length and short values come back whole:
string[] states = { "California", "Iowa", "Ohio", "" };
int n = 5;
foreach (string state in states)
{
string viaSubstring = state.Substring(0, Math.Min(n, state.Length));
string viaRange = state[..Math.Min(n, state.Length)];
Console.WriteLine($"'{state}' -> '{viaSubstring}' | '{viaRange}'");
}
Output:
'California' -> 'Calif' | 'Calif'
'Iowa' -> 'Iowa' | 'Iowa'
'Ohio' -> 'Ohio' | 'Ohio'
'' -> '' | ''
Iowa and Ohio come back as-is, and the empty string stays empty. I use this one-liner whenever input comes from a file or a form.
Get the First N Characters Without Allocating: AsSpan()[..n] vs LINQ Take(n)
If you only need to compare or parse the prefix, slice a ReadOnlySpan<char>. Here it is next to LINQ Take(n):
string product = "Wireless Noise-Canceling Headphones";
ReadOnlySpan<char> span = product.AsSpan()[..8]; // no new string
Console.WriteLine($"Span: {span} (length {span.Length})");
IEnumerable<char> taken = product.Take(8);
Console.WriteLine($"Take: {taken}");
Console.WriteLine($"As string: {new string(taken.ToArray())}");
Console.WriteLine($"Take(100): {string.Concat(product.Take(100)).Length} chars, no exception");
Output:
Span: Wireless (length 8)
Take: System.Linq.Enumerable+IEnumerableSkipTakeIterator`1[System.Char]
As string: Wireless
Take(100): 35 chars, no exception
The span points into the original string, so nothing is copied. Call ToString() only when you really need a string.
Take(8) returns an IEnumerable<char>, which is why printing it shows a type name. You pay for an iterator, a char array and a new string, the same steps as when you convert a char array to a string.
Its one perk is that Take(100) doesn’t throw on a short string. Math.Min gives you that for less work.
Get the First Character of a String: s[0] vs s[..1]
For one character, the indexer and a one-character range differ in type and in how they fail:
string city = "Boston";
char first = city[0]; // a char
string firstStr = city[..1]; // a string
Console.WriteLine($"{first} is {first.GetType().Name}, {firstStr} is {firstStr.GetType().Name}");
string empty = "";
try { Console.WriteLine(empty[0]); }
catch (Exception ex) { Console.WriteLine($"empty[0]: {ex.GetType().Name}"); }
try { Console.WriteLine(empty[..1]); }
catch (Exception ex) { Console.WriteLine($"empty[..1]: {ex.GetType().Name}"); }
Output:
B is Char, B is String
empty[0]: IndexOutOfRangeException
empty[..1]: ArgumentOutOfRangeException
s[0] gives you a char, and s[..1] gives you a string. On an empty string, they throw IndexOutOfRangeException and ArgumentOutOfRangeException.
To test the first letter instead, s.StartsWith('B') is safe on empty strings. Here’s how to check if a string contains a character.
Get the Last Character and Last N Characters of a String
The hat operator ^ counts from the end. Here’s a ZIP+4 code:
string zip = "90210-4417";
Console.WriteLine($"Last char: {zip[^1]}");
Console.WriteLine($"Old style: {zip[zip.Length - 1]}");
Console.WriteLine($"Last 4: {zip[^4..]}");
Output:
Last char: 7
Old style: 7
Last 4: 4417
zip[^1] equals zip[zip.Length - 1], and zip[^4..] takes the last four. You can remove the last character from a string the same way with s[..^1].
How Do You Get the First and Last Letter of a String in C#?
Combine name[0] and name[^1], and skip null or empty values first:
string?[] names = { "Jenna Moore", "Texas", "A", "", null };
foreach (string? name in names)
{
if (string.IsNullOrEmpty(name))
{
Console.WriteLine("(null or empty) -> skipped");
continue;
}
Console.WriteLine($"{name,-12} first: {name[0]} last: {name[^1]}");
}
Output:
Jenna Moore first: J last: e
Texas first: T last: s
A first: A last: A
(null or empty) -> skipped
(null or empty) -> skipped
![C# get first and last letter of a string with name[0] and name[^1], skipping null and empty strings, VS Code output](https://aspdotnethelp.com/wp-content/uploads/2023/06/csharp-get-first-and-last-letter-of-string.png)
A one-letter string like A returns the same character twice. Without the guard, the empty row would throw, which is why you check if a string is null or empty first.
A Reusable Left() and Truncate() String Extension Method
C# has no built-in Left() like VB or Excel. These two extension methods fill the gap on a 20-character receipt line:
using System.Globalization;
var us = CultureInfo.GetCultureInfo("en-US");
var items = new[] { ("Organic Bananas", 1.29m), ("Milk", 3.49m),
("Stainless Steel Water Bottle", 24.99m) };
foreach (var (item, price) in items)
Console.WriteLine($"{item.Truncate(20),-20} {price.ToString("C", us),7} [{item.Left(3)}]");
Console.WriteLine($"null -> '{((string?)null).Left(3)}'");
static class StringExtensions
{
public static string Left(this string? s, int n) =>
string.IsNullOrEmpty(s) ? "" : s[..Math.Min(n, s.Length)];
public static string Truncate(this string? s, int max) =>
s is null ? "" : s.Length <= max ? s : s[..(max - 3)] + "...";
}
Output:
Organic Bananas $1.29 [Org]
Milk $3.49 [Mil]
Stainless Steel W... $24.99 [Sta]
null -> ''

Truncate keeps 17 characters plus ..., so the line is exactly 20. Left returns an empty string for null. To pad short values instead, see how to add leading zeros to a string.
Does Substring Cut Emoji in Half?
Yes. Substring and ranges count UTF-16 chars, and many emoji take two:
using System.Globalization;
using System.Text;
Console.OutputEncoding = Encoding.UTF8;
string city = "Miami " + char.ConvertFromUtf32(0x1F334); // palm tree emoji = 2 chars
var info = new StringInfo(city);
Console.WriteLine($"Length: {city.Length}, text elements: {info.LengthInTextElements}");
string cut = city[..7];
Console.WriteLine($"[..7] ends in a lone surrogate: {char.IsHighSurrogate(cut[^1])}");
string safe = info.SubstringByTextElements(0, Math.Min(7, info.LengthInTextElements));
Console.WriteLine($"StringInfo: {safe}");
Output:
Length: 8, text elements: 7
[..7] ends in a lone surrogate: True
StringInfo: Miami 🌴

StringInfo.SubstringByTextElements counts what your users see as characters. The same trap shows up when you reverse a string in C#.
Which Method Should You Use to Get the First N Characters?
| Task | Code | Short or empty string |
|---|---|---|
| First N characters | s[..n] or s.Substring(0, n) | Throws |
| First N, safely | s[..Math.Min(n, s.Length)] | Returns whole string |
| First N, no allocation | s.AsSpan()[..n] | Throws |
| Last N characters | s[^n..] | Throws |
| Emoji-safe first N | StringInfo.SubstringByTextElements | Throws if N is too big |
In new code I write s[..Math.Min(n, s.Length)], or wrap it in Left(). Use a span in hot loops and StringInfo for user-typed text.
Working with more string slicing? Pick one of these next:
- Remove a particular character from a string
- Remove the last word from a string
- Compare two strings character by character
- Convert a string to a byte array
Frequently Asked Questions
How do I get the first 3 characters of a string in C#?
Use s[..3] or s.Substring(0, 3). If the string can be shorter, use s[..Math.Min(3, s.Length)].
Why does Substring(0, n) throw ArgumentOutOfRangeException?
Because n is larger than the string’s length. The String.Substring documentation on Microsoft Learn lists that exception when startIndex plus length is past the end of the string.
Is there a Left() function in C#?
No. Wrap s[..Math.Min(n, s.Length)] in a small Left() extension method.
How do I get the last character of a string in C#?
Use s[^1], or s[s.Length - 1] before C# 8. Both throw on an empty string.
Is AsSpan faster than Substring for the first N characters?
Yes, when you don’t need a string. AsSpan()[..n] points into the original; Substring copies.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…