C# checked and unchecked — integer overflow explained
Integer arithmetic in C# silently wraps around on overflow by default — adding 1 to int.MaxValue gives int.MinValue, not an error. The checked keyword turns overflow into an OverflowException. The unchecked keyword (the default) explicitly opts out.
The Overflow Problem
int max = int.MaxValue; // 2,147,483,647
int result = max + 1;
Console.WriteLine(result); // -2147483648 — silent wrap-around!No exception, no warning — this is the default (unchecked) behavior.
checked — Throw on Overflow
int max = int.MaxValue;
try
{
int result = checked(max + 1); // throws OverflowException
Console.WriteLine(result);
}
catch (OverflowException ex)
{
Console.WriteLine($"Overflow: {ex.Message}");
// Overflow: Arithmetic operation resulted in an overflow.
}Checked Block
Apply checked to a whole block of code:
checked
{
int a = int.MaxValue;
int b = a + 1; // throws OverflowException
int c = a * 2; // also throws
}unchecked — Explicit Wrap-Around
Use unchecked when you intentionally want wrap-around behavior (e.g., hash code computation, bit manipulation):
unchecked
{
int a = int.MaxValue;
int b = a + 1;
Console.WriteLine(b); // -2147483648 — wrap-around, no exception
}
// unchecked expression
int hash = unchecked(17 * 31 + someValue);Scope Rules
checked/unchecked apply to the immediately-enclosed expressions and blocks, not to method calls inside them:
int Multiply(int a, int b) => a * b; // this runs unchecked regardless
checked
{
int result = Multiply(int.MaxValue, 2); // overflow happens inside Multiply — not caught here!
// To catch it, the multiplication itself must be inside checked
}Compiler Setting — /checked
You can make checked the default for the entire project by adding to your .csproj:
<PropertyGroup>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
</PropertyGroup>
With this set, all arithmetic is checked by default. Use unchecked to opt out for performance-sensitive or intentionally-wrapping code.
All Integer Types Affected
// byte, sbyte, short, ushort, int, uint, long, ulong all overflow
byte b = byte.MaxValue; // 255
Console.WriteLine(unchecked((byte)(b + 1))); // 0 — wraps
checked
{
byte overflow = checked((byte)(b + 1)); // OverflowException
}Conversion Overflow
checked also affects explicit casts between numeric types:
int big = 300;
// Unchecked (default) — silently truncates
byte truncated = (byte)big;
Console.WriteLine(truncated); // 44 — silent data loss
// Checked — throws instead of silently losing data
try
{
byte safe = checked((byte)big);
}
catch (OverflowException)
{
Console.WriteLine("Value 300 does not fit in a byte"); // prints this
}When to Use Each
| Scenario | Use |
|---|---|
| Safety-critical financial/medical calculations | checked |
| Validating user-supplied numeric input | checked |
Hash code computation (GetHashCode) | unchecked |
| Low-level bit manipulation | unchecked |
| Performance-sensitive inner loops | unchecked (default) |
| Project-wide overflow detection during development | /checked compiler flag |
Real-World Example: Safe Counter
public class SafeCounter
{
private int _count;
public void Increment()
{
try
{
_count = checked(_count + 1);
}
catch (OverflowException)
{
throw new InvalidOperationException(
$"Counter exceeded maximum value ({int.MaxValue}).");
}
}
public int Value => _count;
}checked vs Try-Catch for TryParse
Don't confuse overflow with parse errors. Use int.TryParse for string → int conversion failures; use checked for overflow during arithmetic:
// ✅ TryParse handles malformed strings, not arithmetic overflow
if (!int.TryParse(userInput, out int value))
{
Console.WriteLine("Not a valid integer");
return;
}
// ✅ checked handles the calculation that follows
try
{
int doubled = checked(value * 2);
Console.WriteLine(doubled);
}
catch (OverflowException)
{
Console.WriteLine("Result too large for int — use long");
}checked/unchecked Do Not Apply to long + Large Values
When you need values beyond int.MaxValue (2.1 billion), use long (up to ~9.2 × 10¹⁸) or decimal/BigInteger for arbitrary precision:
long bigResult = (long)int.MaxValue + 1;
Console.WriteLine(bigResult); // 2147483648 — correct, no overflow