C# Data Types: int, double, decimal, bool, char and More
Every piece of data in your application has a type — and choosing the right one matters more than you think.
Imagine you're building a warehouse management system.
You need to store:
- The number of items in stock (whole numbers — 0, 1, 2, 3…)
- The price of each item (decimal numbers — $19.99, $45.50)
- The name of each item (text — "Laptop", "Mouse", "Keyboard")
- Whether an item is in stock or not (true/false)
You can't store a price as a whole number — you'd lose the cents. You can't store a name as a number. And you can't store "yes/no" as text without wasting memory and making comparisons slow.
C# built-in data types solve this problem. They give you precise, efficient tools to represent every kind of data your application needs to handle.
What Is It?
The Simple Explanation
Data types are the categories of data that your program can work with. They tell the computer:
- What kind of value you're storing
- How much memory to allocate
- What operations are allowed on that value
The Technical Definition
C# provides a set of built-in data types — predefined types that map directly to underlying types in the Common Type System (CTS) of the .NET platform. These are the atomic building blocks from which every more complex type (classes, structs, records, collections) is composed.
In C#, everything is an object — but the built-in types give you direct, efficient access to the primitive data representations that the runtime understands natively.
Why Does It Exist?
The Problem
Without data types, a computer has no way to know:
- How many bytes of memory to set aside for a value
- How to interpret the bits stored in that memory
- What operations are valid on that value
For example, the byte pattern 01000001 could mean:
- The number 65
- The letter 'A'
- Part of a larger structure
Without a type, the computer can't distinguish between these interpretations.
The Solution
C# provides a rich set of built-in types that give meaning to data and enable the compiler to catch errors before your code ever runs.
C# is a type-safe language. This means the compiler can verify that you're using data correctly — preventing entire categories of bugs that plague dynamically-typed languages.
Big Picture
C# built-in data types divide into two fundamental categories:
Complete Overview Table
| C# Keyword | .NET Type | Size | Range / Purpose | Category |
|---|---|---|---|---|
sbyte | System.SByte | 8 bits | -128 to 127 | Integral |
byte | System.Byte | 8 bits | 0 to 255 | Integral |
short | System.Int16 | 16 bits | -32,768 to 32,767 | Integral |
ushort | System.UInt16 | 16 bits | 0 to 65,535 | Integral |
int | System.Int32 | 32 bits | -2.1 billion to 2.1 billion | Integral |
uint | System.UInt32 | 32 bits | 0 to 4.2 billion | Integral |
long | System.Int64 | 64 bits | -9.2 quintillion to 9.2 quintillion | Integral |
ulong | System.UInt64 | 64 bits | 0 to 18.4 quintillion | Integral |
Int128 | System.Int128 | 128 bits | ±1.7×10³⁸ — .NET 7+ | Integral |
UInt128 | System.UInt128 | 128 bits | 0 to 3.4×10³⁸ — .NET 7+ | Integral |
nint | System.IntPtr | 32/64 bits | Native-sized signed integer | Integral |
nuint | System.UIntPtr | 32/64 bits | Native-sized unsigned integer | Integral |
float | System.Single | 32 bits | ±3.4×10³⁸, ~7 digits precision | Floating-point |
double | System.Double | 64 bits | ±1.7×10³⁰⁸, ~15-17 digits precision | Floating-point |
Half | System.Half | 16 bits | ±65,504, ~3 digits precision — .NET 5+ | Floating-point |
decimal | System.Decimal | 128 bits | ±7.9×10²⁸, 28-29 digits precision | Decimal |
bool | System.Boolean | 8 bits | true or false | Boolean |
char | System.Char | 16 bits | Unicode character (U+0000 to U+FFFF) | Character |
string | System.String | Variable | Sequence of Unicode characters | Reference |
object | System.Object | Variable | Base type of all types | Reference |
DateOnly | System.DateOnly | — | Calendar date without time — .NET 6+ | Struct |
TimeOnly | System.TimeOnly | — | Time of day without date — .NET 6+ | Struct |
How It Works
Let's trace what happens when you declare and use a variable:
int age;
- The compiler knows you need 32 bits (4 bytes) of memory
- The variable is declared but not yet assigned
age = 25;
- The value 25 is stored in the 4-byte memory slot
- Binary representation:
00000000 00000000 00000000 00011001
int newAge = age + 5; // 30
- The compiler knows + is valid for int
- Result 30 is stored in a new 4-byte slot
Simple Example
Here's a beginner-friendly example using the most common types:
// Whole numbers int itemCount = 42; // Decimal numbers (money) decimal itemPrice = 19.99m; // True / False bool isInStock = true; // Single character char rating = 'A'; // Text string itemName = "Mechanical Keyboard"; // C# 14 / .NET 10: target-typed new expression decimal total = new(839.58); // Modern additions DateOnly orderDate = new(2025, 1, 15); TimeOnly orderTime = new(14, 30, 0); Half precisionValue = (Half)0.5;
Real-World Example
Let's see how these types work in a banking application:
public class BankAccount { // Account holder (text) public string HolderName { get; set; } // Account number (up to 19 digits) public long AccountNumber { get; set; } // Balance (money — must use decimal, NOT double) public decimal Balance { get; private set; } // Is the account active? (true/false) public bool IsActive { get; set; } // Date account was opened public DateOnly OpenedDate { get; set; } // Transaction type (enum uses int as underlying type) public enum TransactionType { Deposit = 0, Withdrawal = 1, Transfer = 2 } }
Why decimal for money? Because double can introduce tiny rounding errors (0.1 + 0.2 ≠ 0.3 exactly in binary). For financial calculations, decimal preserves exact decimal precision.
Analogy
Think of data types like different containers in a kitchen:
| C# Type | Kitchen Container | Why It Fits |
|---|---|---|
int | Measuring cup for whole items | Counts items (3 eggs, not 3.5 eggs) |
decimal | Precise kitchen scale | Money needs exact precision (no rounding) |
double | Thermometer | Approximate measurements are fine (temperature) |
bool | On/Off switch | Only two states: yes or no |
char | Single alphabet letter | One character at a time |
string | Notepad | Multiple characters together (text) |
Just as you wouldn't use a kitchen scale to measure a cup of flour (wrong tool), you wouldn't use a double to store money (wrong precision).
Under the Hood
Let's look at what happens in memory when you declare different types:
int x = 10; int y = x; // COPIES the value y = 20; // x is still 10 Memory: x → [ 10 ] y → [ 20 ] (separate copy)
string a = "Hello"; string b = a; // COPIES the reference Memory: a → [ pointer to "Hello" ] b → [ pointer to "Hello" ] (same data!)
This difference matters. With value types, modifying one variable doesn't affect another. With reference types, two variables can point to the same underlying data.
Common Confusion
float vs double vs decimal
This is one of the most common sources of confusion for beginners:
float — 32 bits, ~7 digits precision. Used for graphics, games, and scientific data where memory matters and small errors are acceptable.
double — 64 bits, ~15-17 digits precision. The default for non-money decimal values. Used for calculations where precision matters but exact decimal representation isn't required.
decimal — 128 bits, 28-29 digits precision. Used for money because it accurately represents base-10 fractions like 0.01 (which double cannot do perfectly).
double x = 0.1 + 0.2; // 0.30000000000000004 (NOT 0.3) decimal y = 0.1m + 0.2m; // 0.3 (exactly) Console.WriteLine(x == 0.3); // False! Console.WriteLine(y == 0.3m); // True
Common Mistakes
Mistake 1 — Using double for money
Wrong: double price = 19.99;
Correct: decimal price = 19.99m;
Money requires exact decimal representation. double can introduce rounding errors that accumulate over many transactions.
Mistake 2 — Forgetting the 'm' suffix on decimal literals
// This is a COMPILE ERROR decimal price = 19.99; // Cannot implicitly convert double to decimal // This is correct decimal price = 19.99m; //
Mistake 3 — Choosing int when you need long
If your application stores user IDs, order numbers, or timestamps that can exceed 2.1 billion, use long instead of int. This is especially common in databases and APIs.
When Should I Use It?
Quick Reference Guide
| Scenario | Use This Type | Reason |
|---|---|---|
| Counting items, IDs, ages | int | 32 bits, sufficient for most counts |
| Large counts (users, transactions) | long | 64 bits, won't overflow easily |
| Money, prices, financial data | decimal | Exact decimal precision |
| Scientific calculations, measurements | double | Performance, good precision |
| Game graphics, ML inference (low precision) | Half | 16-bit, fast on GPUs |
| Yes/no, flags, toggles | bool | Only two states |
| Single character, parsing | char | One Unicode character |
| Names, emails, URLs, messages | string | Sequence of characters |
| Calendar dates (no time) | DateOnly | Clear intent, no timezone confusion |
| Time-of-day (no date) | TimeOnly | Clear intent, no date confusion |
| Huge integers (cryptography, big data) | Int128 | 128-bit precision |
| Interop with native code | nint / nuint | Matches CPU word size |
When to use var (implicit typing)
C# allows you to use var to let the compiler infer the type:
var count = 42; // inferred as int var name = "Layerbit"; // inferred as string var price = 19.99m; // inferred as decimal (m suffix)
Good practice: Use explicit types when the type is not obvious from the context. Use var when the type is clearly evident from the right side.
Mental Model
int → Label: "whole number" · Size: 4 bytes · Ops: +, -, ×, ÷
decimal → Label: "money" · Size: 16 bytes · Ops: exact arithmetic
string → Label: "text" · Size: variable · Ops: concat, search, replace
bool → Label: "yes/no" · Size: 1 byte · Ops: &&, ||, !
Choose the right label, and the compiler will protect you from mistakes.
Key Takeaway
- C# built-in data types are the atomic building blocks for all your variables, properties, and method parameters.
- Value types store data directly (int, double, bool, char, decimal, structs).
- Reference types store pointers to data (string, object, dynamic).
- Choose the right type for your data:
- Whole numbers →
int(orlongfor large values) - Money →
decimal(NEVERdouble) - Scientific →
double - Yes/no →
bool - Text →
string - Dates without time →
DateOnly - Time without date →
TimeOnly
- Whole numbers →
- Modern .NET 10 / C# 14 additions include:
Half— 16-bit floating point for GPU/ML workloadsInt128/UInt128— 128-bit integersnint/nuint— native-sized integers for interopDateOnly/TimeOnly— precise date/time separation- Target-typed
newexpressions —decimal x = new(19.99m);
- Understanding types prevents entire categories of bugs — overflow, precision loss, incorrect comparisons, and memory waste.
Check Your Understanding
You've seen how data types work, how to choose the right one, and how value types and reference types differ. Let's see if you can apply this knowledge.
1. You're building a banking application that needs to calculate interest on customer accounts. Which data type should you use for storing the account balance?
Show answer
Correct: C
Why C is correct: decimal is the correct choice for financial data because it accurately represents base-10 fractions (like 0.01) without floating-point rounding errors. When dealing with money, exact precision is non-negotiable — a bank must be able to calculate $0.10 + $0.20 and get exactly $0.30, every time.
Why A is incorrect: double uses binary floating-point representation, which cannot exactly represent many decimal values. For example, 0.1 + 0.2 with double equals approximately 0.30000000000000004, not exactly 0.3. Over millions of transactions, these tiny errors accumulate.
Why B is incorrect: float has even less precision than double (only ~7 decimal digits vs ~15-17). Using float for money would introduce larger rounding errors than double.
Why D is incorrect: long stores whole integers only. Account balances need decimal places (cents), so long cannot represent $19.99.
Reinforcement: This is a fundamental rule in .NET development: never use double or float for money. Always use decimal. Microsoft's own guidelines explicitly recommend decimal for financial calculations.
2. What is the key difference between value types (like int) and reference types (like string) in C#?
Show answer
Correct: B
Why B is correct: This is the fundamental difference. When you declare an int x = 10, the variable x directly contains the value 10 in memory. When you declare a string s = "Hello", the variable s contains a reference (pointer) to where the string "Hello" is actually stored on the heap.
Why A is incorrect: While value types can be more efficient for small data (no heap allocation, no pointer dereferencing), they're not universally faster. Large value types (like big structs) can be slower when copied frequently. Reference types avoid copying large data by sharing references.
Why C is incorrect: This is backwards. Reference types CAN be null by default. Value types CANNOT be null unless they are explicitly made nullable using Nullable<T> or the ? operator (e.g., int? x = null;).
Why D is incorrect: They behave very differently. Assigning one value type variable to another creates a copy. Assigning one reference type variable to another creates two references to the same data.
Reinforcement: Understanding this difference is crucial for avoiding bugs. When you pass a value type to a method, the method receives a copy — changes inside the method don't affect the original. When you pass a reference type, changes inside the method DO affect the original data.
3. In .NET 10 / C# 14, which new built-in type should you use to store a 16-bit floating-point number for GPU or machine learning workloads where memory bandwidth is critical?
Show answer
Correct: C
Why C is correct: System.Half (keyword Half) is a 16-bit floating-point type introduced in .NET 5. It's specifically designed for scenarios where memory bandwidth is critical — such as GPU computing, machine learning inference, and graphics processing — because it uses half the memory of float (32 bits) and quarter of double (64 bits).
Why A is incorrect: float is 32 bits (4 bytes). While float is used in graphics and games, Half provides an additional layer of memory savings when precision requirements are lower.
Why B is incorrect: double is 64 bits (8 bytes). This is actually the OPPOSITE of what you want when memory bandwidth is critical — double uses MORE memory, not less.
Why D is incorrect: decimal is 128 bits (16 bytes) and is designed for financial precision, not for high-performance computing workloads. It would be the worst choice for GPU/ML scenarios.
Reinforcement: Modern .NET includes specialized types for modern workloads. Half, Int128, and nint are all examples of how .NET has evolved to meet the demands of contemporary computing — from machine learning to cryptography to native interoperability.
4. A developer writes the following code:
int x = 10;
int y = x;
y = 20;
Console.WriteLine(x);
What is the output?
Show answer
Correct: A
Why A is correct: int is a value type. When you write int y = x;, C# makes a copy of the value 10 and stores it in y. When you then change y to 20, you're only modifying the copy. The original variable x still contains 10.
Why B is incorrect: This would be the output if int were a reference type (where y would point to the same memory as x). But int is a value type, so modifying y doesn't affect x.
Why C is incorrect: NullReferenceException occurs when you try to access a member of a reference type that is null. int is a value type and cannot be null by default, so no exception will occur.
Why D is incorrect: This is valid C# code. It compiles without any errors.
Reinforcement: This example demonstrates the copy semantics of value types. If you had used a reference type (like a custom class), both variables would point to the same object, and modifying one would affect the other.
5. You need to store the date "March 15, 2025" for a user's birthday. You don't need to store any time information (like 10:30 AM). Which type should you use?
Show answer
Correct: B
Why B is correct: DateOnly (introduced in .NET 6) stores exactly what its name says — only the date, without any time component. This makes your intent clear: "This is a date, not a timestamp." It also avoids the problem of having a meaningless 00:00:00 time attached to a date.
Why A is incorrect: DateTime always includes a time component, even if you set it to midnight. Using DateTime for a birthday would store an unnecessary time value (like 00:00:00) that could cause confusion or timezone issues.
Why C is incorrect: string could store the date as text ("March 15, 2025"), but you lose all type safety and date-specific operations. You can't easily compare dates, sort them, or calculate age from a string without parsing.
Why D is incorrect: TimeOnly stores only the time (like 10:30 AM), not the date. It's the opposite of what you need — you want the date without the time.
Reinforcement: The introduction of DateOnly and TimeOnly in .NET 6 represented a philosophical shift in how .NET handles temporal data. They encourage you to think precisely about what you're storing: a date, a time, or both together (as DateTime). This precision leads to fewer bugs and clearer code.
You've built a solid foundation in understanding C# built-in data types!