AlgoMaster Logo

Variables & Data Types

High Priority17 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Every program needs a way to remember things: the price of a product, the number of items in a cart, whether an order has shipped. In C++, those pieces of remembered data live in variables, and every variable has a type that decides what kind of value it can hold. This lesson covers the basics of declaring variables, the three styles of initialization, the shape of C++'s type system, and how the compiler picks a type when you let it.

What a Variable Is

A variable is a named slot in memory that holds a value. The slot gets a name, the compiler is told what kind of value goes in it, and from then on it can be read or written by name.

The smallest useful example.

stockCount is the variable. It has type int (an integer), and it currently holds the value 12. Whenever the name stockCount appears later in the program, the compiler reads from that slot.

A variable has three things tied together:

  • A name, like stockCount or cartTotal.
  • A type that fixes the shape of values it can hold, like int or double.
  • A value stored in it right now, which can change over time as the program runs.

Once a variable's type is declared, that type is locked in. The value can change as often as needed, but the type can't be swapped. C++ checks every read and write against the declared type at compile time and refuses to compile code that breaks the rule.

The Variable Lifecycle

A variable has a small life of its own. It comes into existence, gets a value, gets used, and then disappears when the surrounding block ends.

The four stages are:

  1. Declare the variable. The compiler reserves storage and remembers the name and the type.
  2. Initialize it with a value. In C++, this often happens on the same line as the declaration.
  3. Use the variable. Read its value, assign new values, pass it to functions.
  4. Scope ends. When execution leaves the block where the variable was declared, the variable is destroyed and its storage is reclaimed.

For local variables inside a function, that last step happens at the closing brace } of the enclosing block. Scope appears again in a section below.

Declaration vs Definition vs Initialization

Three words get thrown around a lot, and they mean slightly different things. Sorting them out early makes a lot of later C++ make sense.

  • Declaration: introduces a name and tells the compiler its type. "There exists a thing called stockCount, and it's an int."
  • Definition: actually creates the storage for the variable. For a local variable, declaration and definition happen on the same line.
  • Initialization: gives the variable its first value.

For most variables, all three happen in one statement.

That single line declares the name stockCount, defines it (the compiler allocates storage for one int), and initializes it to 12. The distinction between declaration and definition matters more when code is split across multiple files and extern is used, which the Build Systems section covers. For now, "declaration" and "definition" are not always the same thing in C++, even if they look the same here.

Initialization deserves more attention, because C++ has not one but three syntaxes for it.

Three Ways to Initialize

C++ allows the initial value of a variable to be written in three different styles. They look different but mostly mean the same thing. The differences become important when narrowing conversions are involved, covered next.

All three produce an int with the given value. Each style has a name and a typical use:

StyleSyntaxNotes
Copy initializationint x = 5;The classic form. Looks like assignment.
Direct initializationint x(5);Function-call-looking form. Common with constructors.
Brace initializationint x{5};Added in C++11. Also called uniform initialization.

The brace form is the newest of the three. It was added in C++11 partly because the language needed one consistent way to initialize any kind of thing: a built-in type, a class, an array, a std::vector. The other two forms have quirks that show up in odd corners of the language, and brace initialization avoids most of them.

Why Brace Initialization Is Preferred

The best reason to prefer {} over = or () is that brace initialization refuses to perform a narrowing conversion. A narrowing conversion is one that could lose information, like squeezing a double into an int. With copy or direct initialization, C++ truncates the fractional part without complaint. With brace initialization, the compiler raises an error.

Uncommenting the third line and rebuilding with g++ -std=c++17 produces an error like:

The compiler is saying: a double was put into an int, and that loses information. To allow it, say so explicitly. This is the kind of bug that's painful in production: a price of 29.99 becomes a stored value of 29, the customer is charged less, and nobody notices until the books don't balance. Brace initialization catches it at compile time.

For that reason alone, most modern C++ style guides recommend int x{5}; over int x = 5; for new code. This course follows that recommendation, with one exception: when copy initialization makes intent clearer (especially for simple integer or bool values), it appears too. C++ is flexible enough that all three styles appear in real code.

Default Initialization

A variable can be declared without any value:

For a built-in type like int, this is called default initialization, and the result depends on where it appears. A local variable declared this way has an indeterminate value: whatever bit pattern happened to be sitting in that memory address before. Reading from it before assignment is undefined behavior and should never happen. Variables declared at namespace scope (outside any function) are zero-initialized by the rules of the language, so they start at 0.

This rule is a friction point for newcomers to C++. Other languages either initialize to zero by default or refuse to compile the read. C++ does neither. The compiler may warn, but it's not required to. The safe habit is simple: always initialize local variables when they're declared.

An empty pair of braces, {}, is the modern way to request "the default value for this type". For built-in number types, that's zero. For a bool, it's false. For a class type, it calls the default constructor. That pattern appears repeatedly later.

The Assignment Operator

Once a variable exists, a new value can be stored in it with the assignment operator =. Assignment is not the same as initialization. Initialization happens once, at the point of declaration. Assignment can happen any number of times after that.

Every assignment overwrites the previous value. The variable's type doesn't change, only the contents do. Assigning a value that doesn't match the type results in either a conversion or a compile error, depending on the types involved.

C++ Is Statically Typed

C++ is a statically typed language. The type of every variable is decided at compile time, based on its declaration, and the compiler checks every read and write against that type. A value of one type can't be stored in a variable of another type without an explicit conversion, and a variable's type can't change after declaration.

What's wrong with this code?

The compiler reports something like:

A string literal can't go into an int. The two types are unrelated, and there's no automatic conversion between them.

Fix: Either keep the variable an int and assign a number, or use a different type that holds text.

That trade-off (more rules up front, fewer surprises later) is a deliberate part of the language's design. C++ catches a lot of type mistakes before the program ever runs.

The auto Keyword

Sometimes the type on the right side of an initialization is obvious, and writing it again on the left is redundant. Since C++11, the auto keyword lets the compiler figure out the type.

auto doesn't make C++ dynamically typed. The compiler picks a single type at the point of declaration and locks it in, just as if it had been written out. auto stockCount = 50; is identical to int stockCount = 50; in every way that matters at runtime.

This is an intro-level look at auto. The full picture (how it deduces types, when it strips references and const, when to use it for readability) lives in the Modern C++ Features section. The next several lessons mostly write types explicitly so the types stay visible during learning.

auto deduces the type from the initializer, so auto x; (without an initializer) doesn't compile. The compiler has nothing to deduce from.

How the Compiler Picks a Type from Literals

A number or a piece of text written directly in source code is called a literal. The compiler looks at the form of the literal and decides what type it has. This matters because auto uses the literal's type, and because the type affects how the value behaves in arithmetic.

A short tour of the common literals.

LiteralDeduced typeNotes
42intPlain integer literal.
42LlongTrailing L makes it long.
42Uunsigned intTrailing U makes it unsigned.
3.14doubleDecimal literals default to double, not float.
3.14ffloatTrailing f forces float.
'A'charSingle quotes, exactly one character.
"Hello"const char[6]Double quotes, a C-string. Convertible to std::string.
true, falseboolThe two boolean literal values.

Two points are worth pinning down.

First, 3.14 is a double, not a float. auto rating = 4.5; deduces a double. For a float, write 4.5f.

Second, 'A' (single quotes) and "A" (double quotes) are not the same type. 'A' is a single char. "A" is a string containing one character plus a null terminator. The Arrays & Strings section covers the difference; for now, the rule: single quotes for one character, double quotes for text.

C++'s Type System at a Glance

C++ has a layered type system. At the bottom are the built-in types the language defines directly. Above that, the standard library adds a large collection of useful types. And on top of all of it, user-defined types come from struct, class, and enum.

The diagram captures the broad shape. Built-in types are the ones the language has had since C: integer types, floating-point types, char, bool, and a few odd ones like void. Everything else, including library types like std::string, is technically a user-defined type. The library is a particularly well-tested set of them.

Built-In Types

The built-in types fall into a few groups:

  • Integral types for whole numbers and characters. This includes bool, char, short, int, long, and long long. Even though char and bool feel like they should be in their own category, the language groups them with the integers because they're stored as small integers internally.
  • Floating-point types for numbers with a decimal part: float, double, and long double.
  • `void`, a special type that means "no value". It appears as the return type of a function that doesn't return anything.

The built-in types break down into integers, floats, and void.

User-Defined Types

Beyond the built-in types, user-defined types compose new shapes. C++ provides several tools for this:

  • `struct` and `class` bundle related data (and behavior) together. A Product struct might hold a name, a price, and a stock count.
  • `enum` and `enum class` name a fixed set of values, like the states an order can be in: Placed, Shipped, Delivered, Cancelled.
  • The standard library provides a long list of ready-made user-defined types: std::string for text, std::vector for resizable arrays, std::map for key-value lookups, and many more.

Each of these has a whole section devoted to it later in the course. The Enums & Structs section covers enum and struct. The Object-Oriented Programming section covers class. The Standard Template Library section covers the library containers. For this lesson, the point is that the type system is open and new types can be added.

Scope: Where a Variable Is Visible

A variable is only visible inside the block where it was declared. A block is anything between a matching pair of curly braces { }. When execution leaves the block, the variable is destroyed and the name becomes invalid.

cartItems is declared inside main, so it's visible everywhere in main. discount is declared inside the if block, so it only exists between that block's braces. Reading discount after the closing } is a compile error: the name is no longer in scope.

This is called block scope, the most common kind of scope. Blocks nest, so an inner block can see the variables declared in an outer block, but not the other way around. The variable's lifetime tracks its scope: when the closing brace runs, the variable is destroyed, and any cleanup the type defines runs at that moment. That cleanup hook becomes a big deal in the RAII coverage in the Memory Management section.

The scope discussion stays light here. Function scope, namespace scope, and global scope each have their own rules that come later.

Putting It Together

A single small program that exercises everything covered above: brace initialization, multiple types, auto, and block scope working together.

A few details. customerName, cartItems, subtotal, and isMember all use brace initialization. shippingFee uses auto and gets deduced as double (the conditional expression has two double operands). total lives inside the inner block, so it's only visible there. And bool prints as 1 for true and 0 for false by default, which is a small detail that recurs in the input/output lesson.

Quiz

Variables & Data Types Quiz

10 quizzes