← Open in the full interactive course (progress tracking, search & 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:

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:

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.

The key insight

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:

For example, the byte pattern 01000001 could mean:

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.

Type Safety

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:

BUILT-IN DATA TYPES
Value Types
Store data directly in the variable
Examples: int, double, bool, char, decimal
Reference Types
Store a reference (pointer) to data
Examples: string, object, dynamic

Complete Overview Table

C# Keyword .NET Type Size Range / Purpose Category
sbyteSystem.SByte8 bits-128 to 127Integral
byteSystem.Byte8 bits0 to 255Integral
shortSystem.Int1616 bits-32,768 to 32,767Integral
ushortSystem.UInt1616 bits0 to 65,535Integral
intSystem.Int3232 bits-2.1 billion to 2.1 billionIntegral
uintSystem.UInt3232 bits0 to 4.2 billionIntegral
longSystem.Int6464 bits-9.2 quintillion to 9.2 quintillionIntegral
ulongSystem.UInt6464 bits0 to 18.4 quintillionIntegral
Int128System.Int128128 bits±1.7×10³⁸ — .NET 7+Integral
UInt128System.UInt128128 bits0 to 3.4×10³⁸ — .NET 7+Integral
nintSystem.IntPtr32/64 bitsNative-sized signed integerIntegral
nuintSystem.UIntPtr32/64 bitsNative-sized unsigned integerIntegral
floatSystem.Single32 bits±3.4×10³⁸, ~7 digits precisionFloating-point
doubleSystem.Double64 bits±1.7×10³⁰⁸, ~15-17 digits precisionFloating-point
HalfSystem.Half16 bits±65,504, ~3 digits precision — .NET 5+Floating-point
decimalSystem.Decimal128 bits±7.9×10²⁸, 28-29 digits precisionDecimal
boolSystem.Boolean8 bitstrue or falseBoolean
charSystem.Char16 bitsUnicode character (U+0000 to U+FFFF)Character
stringSystem.StringVariableSequence of Unicode charactersReference
objectSystem.ObjectVariableBase type of all typesReference
DateOnlySystem.DateOnlyCalendar date without time — .NET 6+Struct
TimeOnlySystem.TimeOnlyTime of day without date — .NET 6+Struct

How It Works

Let's trace what happens when you declare and use a variable:

DECLARATION → ALLOCATION → ASSIGNMENT → OPERATION
1. YOU DECLARE A VARIABLE
int age;
2. YOU ASSIGN A VALUE
age = 25;
3. YOU PERFORM AN OPERATION
int newAge = age + 5;  // 30

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 itemsCounts items (3 eggs, not 3.5 eggs)
decimal Precise kitchen scaleMoney needs exact precision (no rounding)
double ThermometerApproximate measurements are fine (temperature)
bool On/Off switchOnly two states: yes or no
char Single alphabet letterOne character at a time
string NotepadMultiple 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:

MEMORY ALLOCATION DIFFERENCE
VALUE TYPE: int
int x = 10;
int y = x;    // COPIES the value
y = 20;       // x is still 10

Memory:
  x → [ 10 ]
  y → [ 20 ]  (separate copy)
REFERENCE TYPE: string
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:

The three decimal types

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, agesint32 bits, sufficient for most counts
Large counts (users, transactions)long64 bits, won't overflow easily
Money, prices, financial datadecimalExact decimal precision
Scientific calculations, measurementsdoublePerformance, good precision
Game graphics, ML inference (low precision)Half16-bit, fast on GPUs
Yes/no, flags, togglesboolOnly two states
Single character, parsingcharOne Unicode character
Names, emails, URLs, messagesstringSequence of characters
Calendar dates (no time)DateOnlyClear intent, no timezone confusion
Time-of-day (no date)TimeOnlyClear intent, no date confusion
Huge integers (cryptography, big data)Int128128-bit precision
Interop with native codenint / nuintMatches 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

Data Type = A Label + A Container Size + Allowed Operations

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


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!


dotnetmadeeasy.com — Learn C# and .NET, the right way.