Home C# / .NET C# Data Types and Variables Explained — Types, Declarations and Real Mistakes

C# Data Types and Variables Explained — Types, Declarations and Real Mistakes

In Plain English 🔥
Think of a variable like a labelled box in your bedroom. You decide what the box is for — shoes only, books only, or loose change — and that decision is the data type. Once you label a box 'shoes', you can't stuff a book in it without consequences. C# works exactly the same way: you name a box (the variable), tell the program what kind of thing goes inside (the data type), and from that point on the program holds you to it. That structure is what stops your program from accidentally putting a person's age where their name should be.
⚡ Quick Answer
Think of a variable like a labelled box in your bedroom. You decide what the box is for — shoes only, books only, or loose change — and that decision is the data type. Once you label a box 'shoes', you can't stuff a book in it without consequences. C# works exactly the same way: you name a box (the variable), tell the program what kind of thing goes inside (the data type), and from that point on the program holds you to it. That structure is what stops your program from accidentally putting a person's age where their name should be.

Every program ever written — from a simple calculator to a banking system — has one job in common: it has to remember things while it's running. It needs to remember a user's name, their account balance, whether they're logged in, and a hundred other details. Without a way to store and label that information, your code is just a series of commands that forgets everything the moment it moves on. Data types and variables are the very first tool that gives your program memory.

The problem they solve is precision. Imagine a hospital system that stores a patient's age as text instead of a number. Suddenly you can't calculate their dosage, you can't sort patients by age, and you can't do any arithmetic at all. Data types prevent that chaos by forcing you — and the compiler — to agree upfront on exactly what kind of information each piece of data is. C# is a strongly-typed language, which means it checks these rules before your code even runs, catching bugs at compile time rather than in production.

By the end of this article you'll know how to declare any variable in C#, choose the right data type for the job, understand the critical difference between value types and reference types, and avoid the three mistakes that trip up almost every beginner. You'll have runnable code you can drop straight into a project and a mental model that will stick with you for years.

What Is a Variable and How Do You Declare One in C#?

A variable is a named slot in your computer's memory that holds a value. You create one by writing the data type first, then the name you want to give it, and optionally an initial value. That three-part pattern — type, name, value — is the foundation of almost every line of C# you'll ever write.

The name you choose matters. Call it something that describes what it holds. playerScore tells you everything; s tells you nothing. C# names are case-sensitive, so PlayerScore and playerScore are two completely different variables — a fact that causes real bugs when you're not careful.

You can declare a variable and assign it later, or do both at once. If you declare without assigning, C# won't let you read it until you give it a value — this is the compiler protecting you from reading garbage memory. Think of it like the labelled box again: the box exists on your shelf, but you can't ship it until you actually put something inside.

C# also gives you the var keyword, which lets the compiler figure out the type from whatever you assign. It's not 'no type' — it's shorthand. The type is still locked in at compile time; you just don't have to write it out explicitly.

VariableDeclarations.cs · CSHARP
123456789101112131415161718192021222324252627282930313233
using System;

class VariableDeclarations
{
    static void Main()
    {
        // Explicit declaration: type is written out by hand
        // 'int' means whole numbers only — no decimals
        int playerScore = 0;

        // String holds text — always wrap text values in double quotes
        string playerName = "Alex";

        // bool holds only true or false — perfect for on/off flags
        bool isLoggedIn = true;

        // 'var' lets the compiler infer the type from the right-hand side
        // The compiler sees 3.14 and decides this is a 'double'
        var piApproximation = 3.14;

        // You can declare first and assign later
        // But you CANNOT read it before assigning — the compiler will refuse
        int livesRemaining;
        livesRemaining = 3; // assignment happens here, now it's safe to use

        // Printing the values to the console so we can see what's stored
        Console.WriteLine($"Player: {playerName}");
        Console.WriteLine($"Score: {playerScore}");
        Console.WriteLine($"Lives: {livesRemaining}");
        Console.WriteLine($"Logged in: {isLoggedIn}");
        Console.WriteLine($"Pi approx: {piApproximation}");
    }
}
▶ Output
Player: Alex
Score: 0
Lives: 3
Logged in: True
Pi approx: 3.14
⚠️
Pro Tip: Use var for long generic types, explicit types everywhere elseReach for `var` when the type is obvious from the right-hand side (e.g. `var message = "Hello"`) or when the type name is painfully long (e.g. `Dictionary>`). For simple primitive types like `int` or `bool`, write the type explicitly — it makes your code easier to read for the next person, which is usually you, six months later.

The C# Built-In Data Types You'll Use Every Day

C# ships with a set of built-in data types that cover every common category of data. Each one maps to a .NET type under the hood, but C# gives them friendlier lowercase aliases — int instead of System.Int32, for example. They're identical; the alias is just less typing.

The most important split to understand is between whole numbers and decimal numbers. int holds whole numbers from about -2.1 billion to +2.1 billion. If you need decimals, you have three choices: float (7 digits of precision, uses an 'f' suffix), double (15-16 digits, the default for decimals), and decimal (28-29 digits, designed for money). For currency, always use decimalfloat and double use binary floating point, which can produce rounding errors that are catastrophic in financial software.

char holds a single character — one letter, one digit, one emoji. string holds any sequence of characters. bool holds true or false. long is like int but with a much bigger range — useful for things like Unix timestamps or row counts in enormous databases.

Choosing the right type isn't pedantry. It determines how much memory is used, what operations are legal, and what bugs you might accidentally introduce.

BuiltInDataTypes.cs · CSHARP
1234567891011121314151617181920212223242526272829303132333435363738
using System;

class BuiltInDataTypes
{
    static void Main()
    {
        // INTEGER TYPES — whole numbers, no decimals
        byte playerLevel = 42;            // 0 to 255 — small non-negative numbers
        short temperatureCelsius = -18;   // -32,768 to 32,767
        int worldPopulationMillions = 8100; // most common integer type
        long distanceToStarInMeters = 9_460_730_472_580_800L; // 'L' suffix = long literal
        // Note: underscores in number literals are just for readability — C# ignores them

        // FLOATING POINT TYPES — numbers with a decimal point
        float bodyTemperatureF = 98.6f;   // 'f' suffix required for float literals
        double gravitationalConstant = 6.674e-11; // scientific notation works too
        decimal productPrice = 19.99m;    // 'm' suffix required — use for all money values

        // TEXT TYPES
        char bloodType = 'O';             // Single quotes for a single character
        string customerName = "Jordan";   // Double quotes for text sequences

        // BOOLEAN TYPE
        bool hasPremiumSubscription = false; // Only ever true or false

        // Showing the type AND value together using GetType()
        Console.WriteLine($"playerLevel is {playerLevel} (type: {playerLevel.GetType().Name})");
        Console.WriteLine($"productPrice is {productPrice:C} (type: {productPrice.GetType().Name})");
        Console.WriteLine($"customerName is '{customerName}' (type: {customerName.GetType().Name})");
        Console.WriteLine($"gravitationalConstant is {gravitationalConstant} (type: {gravitationalConstant.GetType().Name})");
        Console.WriteLine($"hasPremiumSubscription is {hasPremiumSubscription} (type: {hasPremiumSubscription.GetType().Name})");

        // Showing the minimum and maximum values built into each type
        Console.WriteLine($"\nMax int value: {int.MaxValue}");
        Console.WriteLine($"Max long value: {long.MaxValue}");
        Console.WriteLine($"Max decimal precision: {decimal.MaxValue}");
    }
}
▶ Output
playerLevel is 42 (type: Byte)
productPrice is $19.99 (type: Decimal)
customerName is 'Jordan' (type: String)
gravitationalConstant is 6.674E-11 (type: Double)
hasPremiumSubscription is False (type: Boolean)

Max int value: 2147483647
Max long value: 9223372036854775807
Max decimal precision: 79228162514264337593543950335
⚠️
Watch Out: Never use float or double for moneyTry this in your head: what is 0.1 + 0.2? In maths, 0.3. In a `double`, it's 0.30000000000000004 — because floats and doubles store values in binary, and 0.1 can't be represented exactly in binary, just like 1/3 can't be written exactly in decimal. For any financial calculation, use `decimal`. It's slower, but it's exact. The extra milliseconds are worth more than a penny rounding error on a customer's invoice.

Value Types vs Reference Types — The Most Important Concept on This Page

This is the concept that separates people who debug their code confidently from people who stare at a screen wondering why their variable changed on its own. Every C# type is either a value type or a reference type, and the difference is about what gets copied when you assign one variable to another.

A value type stores the actual data directly. When you copy it to a new variable, you get a fresh independent copy. Change the copy, the original is untouched. All the numeric types, bool, char, and struct types are value types. Think of it like writing a phone number on two separate sticky notes — if you scribble on one, the other is fine.

A reference type stores the address of the data, not the data itself. When you 'copy' a reference type, both variables now point at the same object in memory. Change it through one variable, and the other variable reflects the change too — because they're both looking at the same thing. string, arrays, and classes are reference types. Think of it like two people both holding a map to the same house. If someone repaints that house, both maps now lead to a painted house.

string in C# is technically a reference type, but it behaves like a value type because strings are immutable — once created, a string's value never changes. When you 'modify' a string, C# actually creates a brand new string in memory.

ValueVsReferenceTypes.cs · CSHARP
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
using System;

// A simple class — classes are always reference types
class ShoppingCart
{
    public int ItemCount; // public field so we can access it directly in this example
}

class ValueVsReferenceTypes
{
    static void Main()
    {
        // ── VALUE TYPE DEMO ──────────────────────────────────────────
        int originalScore = 100;
        int backupScore = originalScore; // A completely independent COPY is made

        backupScore = 999; // We change only the copy

        // The original is untouched — each int lives in its own memory slot
        Console.WriteLine($"originalScore: {originalScore}"); // still 100
        Console.WriteLine($"backupScore:   {backupScore}");   // 999

        Console.WriteLine();

        // ── REFERENCE TYPE DEMO ──────────────────────────────────────
        ShoppingCart customerCart = new ShoppingCart();
        customerCart.ItemCount = 3;

        // This does NOT copy the cart — it copies the ADDRESS of the cart
        // Both variables now point at the SAME object in memory
        ShoppingCart checkoutCart = customerCart;

        checkoutCart.ItemCount = 7; // We change it through checkoutCart...

        // ...but customerCart reflects the change because they share one object
        Console.WriteLine($"customerCart.ItemCount: {customerCart.ItemCount}"); // 7 !
        Console.WriteLine($"checkoutCart.ItemCount: {checkoutCart.ItemCount}"); // 7

        Console.WriteLine();

        // ── STRING IMMUTABILITY DEMO ─────────────────────────────────
        string originalGreeting = "Hello";
        string modifiedGreeting = originalGreeting;

        // This looks like we're changing modifiedGreeting in place
        // but C# actually creates a BRAND NEW string and points modifiedGreeting at it
        modifiedGreeting = modifiedGreeting + ", World";

        Console.WriteLine($"originalGreeting: {originalGreeting}"); // still "Hello"
        Console.WriteLine($"modifiedGreeting: {modifiedGreeting}"); // "Hello, World"
    }
}
▶ Output
originalScore: 100
backupScore: 999

customerCart.ItemCount: 7
checkoutCart.ItemCount: 7

originalGreeting: Hello
modifiedGreeting: Hello, World
🔥
Interview Gold: Stack vs HeapValue types are (usually) stored on the stack — fast, automatically cleaned up when a method returns. Reference types live on the heap — managed by the garbage collector. Interviewers love asking 'where does a value type live?' The technically precise answer is: value types live wherever they're declared. A value type inside a class instance lives on the heap alongside that instance. But the core rule — value types copy data, reference types copy addresses — always holds true regardless of where they're stored.

Type Conversion — Safely Moving Data Between Types

Sometimes you have data in one type and you need it in another. A user types their age into a text box — that comes in as a string. You need to do maths with it — that requires an int. This is type conversion, and C# gives you three ways to do it.

Implicit conversion happens automatically when C# can guarantee no data will be lost. Going from int to long is safe — a long can hold everything an int can, and more. Going from float to double is safe for the same reason. C# just does it for you without complaining.

Explicit conversion (casting) is when you force the conversion and accept that data might be lost. Going from double to int chops off the decimal part — 9.9 becomes 9, not 10. You signal this intent by putting the target type in brackets before the value: (int)someDouble. The compiler won't do this silently because you'd lose data without knowing it.

Parsing is for converting strings to numeric types. int.Parse("42") works great when the string is definitely a valid number. int.TryParse("42", out int result) is the safer version — it returns false instead of throwing an exception if the string is something unexpected like "hello" or an empty field from a form. In real applications, always use TryParse for user input.

TypeConversion.cs · CSHARP
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
using System;

class TypeConversion
{
    static void Main()
    {
        // ── IMPLICIT CONVERSION ──────────────────────────────────────
        // int fits perfectly inside a long — no data can be lost, so C# allows it silently
        int monthsInYear = 12;
        long secondsInYear = monthsInYear * 30L * 24L * 60L * 60L; // int auto-promotes to long
        Console.WriteLine($"Seconds in a year (approx): {secondsInYear}");

        // ── EXPLICIT CONVERSION (CASTING) ────────────────────────────
        double exactAverageScore = 87.9;
        // We MUST cast explicitly because going from double to int loses the decimal
        // The cast does NOT round — it truncates (chops off) the decimal part
        int roundedScore = (int)exactAverageScore;
        Console.WriteLine($"Exact score: {exactAverageScore}");
        Console.WriteLine($"After casting to int: {roundedScore}"); // 87, not 88!

        // If you want proper rounding, use Math.Round before casting
        int properlyRoundedScore = (int)Math.Round(exactAverageScore);
        Console.WriteLine($"Properly rounded: {properlyRoundedScore}"); // 88

        Console.WriteLine();

        // ── PARSING — converting strings to numbers ──────────────────
        string userInputAge = "29";     // Imagine this came from a text box
        int parsedAge = int.Parse(userInputAge); // Works, but throws if input is invalid
        Console.WriteLine($"Parsed age: {parsedAge} (type: {parsedAge.GetType().Name})");

        // ── TRYPARSE — the safe, production-ready approach ───────────
        string suspiciousInput = "twenty"; // User typed a word instead of a number

        // TryParse returns true/false and writes the result into 'parsedValue' via 'out'
        bool parseSucceeded = int.TryParse(suspiciousInput, out int parsedValue);

        if (parseSucceeded)
        {
            Console.WriteLine($"Parsed successfully: {parsedValue}");
        }
        else
        {
            // This branch runs — no exception thrown, we handle it gracefully
            Console.WriteLine($"Could not parse '{suspiciousInput}' as an integer. Please enter digits only.");
        }

        // ── Convert class — another option for common conversions ─────
        double pi = 3.14159;
        string piAsText = Convert.ToString(pi); // Converts double to string
        Console.WriteLine($"Pi as a string: '{piAsText}'");
    }
}
▶ Output
Seconds in a year (approx): 31104000
Exact score: 87.9
After casting to int: 87
Properly rounded: 88

Parsed age: 29 (type: Int32)
Could not parse 'twenty' as an integer. Please enter digits only.
Pi as a string: '3.14159'
⚠️
Watch Out: int.Parse throws, TryParse doesn'tIf you call `int.Parse("abc")` your program throws a `FormatException` and crashes unless you've wrapped it in a try-catch. In any situation where the input comes from a user, a file, or a network — in other words, anywhere you don't control — use `int.TryParse` instead. It's not slower, it's not harder to write, and it's the difference between a robust application and one that crashes the moment a user makes a typo.
TypeCategorySize (bytes)Range / PrecisionTypical Use Case
boolValue (struct)1true or false onlyFlags, conditions, toggles
byteValue (struct)10 to 255Raw binary data, pixel values
intValue (struct)4-2,147,483,648 to 2,147,483,647Counts, indexes, ages — everyday whole numbers
longValue (struct)8±9.2 × 10^18Timestamps, large IDs, file sizes in bytes
floatValue (struct)4~7 significant digitsGame graphics, sensors — where speed beats precision
doubleValue (struct)8~15-16 significant digitsScientific calculations, general decimal maths
decimalValue (struct)16~28-29 significant digitsCurrency, financial data — where exactness is mandatory
charValue (struct)2One Unicode character (U+0000 to U+FFFF)Single characters, character parsing
stringReference (class)VariableUp to ~2 billion charactersNames, messages, any text
objectReference (class)VariableCan hold any type (boxing/unboxing applies)Generic containers, reflection, legacy APIs

🎯 Key Takeaways

  • Every variable in C# has a fixed type set at compile time — C# is strongly typed, which means type errors are caught before your program runs, not after it crashes in production.
  • Use decimal for any monetary value without exception — float and double use binary floating point and will silently introduce rounding errors into financial calculations.
  • Value types (int, bool, struct, etc.) copy their data when assigned; reference types (class, string, array) copy only the memory address — accidentally sharing a reference type between two variables is one of the most common sources of hard-to-find bugs.
  • Always use int.TryParse() instead of int.Parse() when the input comes from anywhere outside your code — user forms, files, APIs — because Parse throws an exception on bad input while TryParse returns false, letting you handle the error gracefully.

⚠ Common Mistakes to Avoid

  • Mistake 1: Using double for currency calculations — Symptom: You get results like $19.999999999999996 instead of $20.00 when adding prices, and totals drift over time — Fix: Always declare money variables as decimal and use the 'm' suffix on literals, e.g. decimal price = 9.99m;. The decimal type uses base-10 arithmetic internally, eliminating the binary rounding errors that plague float and double.
  • Mistake 2: Calling int.Parse() on user input without validation — Symptom: Your program crashes with an unhandled System.FormatException the moment a user types a letter, a space, or leaves a field empty — Fix: Replace int.Parse(input) with int.TryParse(input, out int result) and check the boolean return value before using result. This keeps your application alive and lets you show the user a friendly error message instead.
  • Mistake 3: Assuming that assigning a class (reference type) to a new variable creates an independent copy — Symptom: You 'copy' an object, modify the copy, and the original changes too — which looks like a ghost is editing your data — Fix: Understand that assigning a reference type copies the pointer, not the object. To get a true independent copy you need to either implement a Clone() method, use a copy constructor, or switch to an immutable design. For simple data containers, consider using a struct (value type) instead of a class so copies are automatic.

Interview Questions on This Topic

  • QWhat is the difference between a value type and a reference type in C#? Can you give an example of each and explain what happens in memory when you assign one to another variable?
  • QWhy would you use decimal instead of double when working with financial data? What specific problem does double have that decimal avoids?
  • QWhat does the var keyword actually do in C#? Does it create a dynamically typed variable? What is the difference between var in C# and var in JavaScript?

Frequently Asked Questions

What is the difference between int and long in C#?

Both hold whole numbers, but int is 32 bits and can store values up to about 2.1 billion, while long is 64 bits and can hold up to about 9.2 quintillion. Use int for everyday counts and indexes where the value won't exceed 2 billion. Switch to long when you're working with things like Unix timestamps, database row counts in massive tables, or any value that could realistically exceed the int ceiling. You can check the exact limits with int.MaxValue and long.MaxValue.

What does the var keyword do in C# and is it bad practice?

The var keyword tells the C# compiler to figure out the type from whatever you assign on the right-hand side. The type is still fixed at compile time — var is not dynamic typing. It's not bad practice when used sensibly: use it when the type is already obvious from context (e.g. var invoice = new Invoice()) or when the type name is very long. Avoid it for simple primitives like int or bool where writing the type out explicitly makes the code easier to scan at a glance.

Why is string a reference type but it behaves like a value type in C#?

Strings are reference types under the hood, stored on the heap, but C# makes them immutable — once a string is created, its contents can never change. Every operation that appears to 'modify' a string (concatenation, ToUpper, Replace, etc.) actually creates a brand new string object in memory and returns that. This immutability means two string variables can never accidentally corrupt each other's data, so strings effectively behave like value types even though they aren't. This is a deliberate design decision to make strings safe and predictable.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousIntroduction to C#Next →Control Flow in C#
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged