GDKB

Data Types

Beginner15 Minutes readUpdated July 21, 20261 related concept

A data type describes what kind of value a variable can hold and what operations are valid on it. It tells the compiler (or runtime) how many bytes to reserve, how to interpret those bytes, and which operations — addition, comparison, indexing — make sense. Every variable declaration pairs a name with a data type; choosing the right one is the first design decision every variable requires.

On this page

Data Types

Summary

A data type describes what kind of value a variable can hold and what operations are valid on it. It tells the compiler (or runtime) how many bytes to reserve, how to interpret those bytes, and which operations — addition, comparison, indexing — make sense. Every variable declaration pairs a name with a data type; choosing the right one is the first design decision every variable requires.

Mental Model

If a variable is a labeled box (see Variables), the data type is the box's shape and size, decided before anything is put inside. A box shaped for a whole number cannot hold a paragraph of text, and a box shaped for a single true/false flag cannot hold a 3D position.

Types come in two families:

  • Value types ("plain boxes") — the box directly contains its data. Copying the box copies the contents; each copy is independent. int, float, bool, and struct types (Vector3) behave this way.
  • Reference types ("boxes holding an address") — the box contains directions to where the real data lives elsewhere. Copying the box copies the directions, not the destination — two boxes can point at the same underlying object, so changing the object through one box is visible through the other. Classes and arrays behave this way in C# and C++ (via pointers/references).

Knowing which family a type belongs to is the single most useful thing to know about it, because it predicts what happens on assignment.

Core Explanation

Data types are typically grouped as:

  • Primitive / built-in types — the language's smallest building blocks: integers (int, long), floating point (float, double), boolean (bool), and character (char). These are almost always value types, fixed in size, and directly supported by the CPU.
  • Composite / structured types — built from primitives: struct (C#) / struct-or-class (C++) group related fields together (Vector3 is three floats). Whether a composite type is a value type or reference type is a deliberate design choice, not automatic — C#'s struct is a value type, its class is a reference type, holding the same fields either way.
  • Reference / object types — class instances, arrays, strings (in most languages). The variable holds a reference to memory elsewhere (commonly heap-allocated), not the data itself.
  • User-defined types — enums (a closed set of named values, e.g. Direction { Up, Down, Left, Right }), and the classes/structs the program itself declares (see Classes).

Static typing (C#, C++) checks that a value's type matches its variable's declared type at compile time — a string cannot be assigned to an int variable, and the program will not build until that is fixed. This converts an entire category of runtime bugs into compile-time errors, which is the primary value proposition of a static type system.

Type size matters for performance and memory: an int is 4 bytes, a long is 8; choosing the smallest type that can safely hold every value the variable will ever need reduces memory footprint, which matters at scale (thousands of entities, each with several fields).

Implicit vs. explicit conversion. Some type conversions happen automatically ("widening", e.g. int to float, because no information is lost). Others require an explicit cast (float to int, because the fractional part is discarded) — the language forces the programmer to write the cast so data loss is visible in the code, not silent.

Syntax / Examples

C# (Unity)

csharp
int score = 0;
float speed = 5.5f;
bool isGrounded = true;
char grade = 'A';
string playerName = "Hero";

// Composite (value type)
Vector3 position = new Vector3(0f, 1f, 0f);

// User-defined enum
enum Direction { Up, Down, Left, Right }
Direction facing = Direction.Up;

// Explicit conversion — fractional part is discarded, not rounded
int wholeSpeed = (int)speed; // 5

C++ (Unreal)

cpp
int32 Score = 0;
float Speed = 5.5f;
bool bIsGrounded = true;
FString PlayerName = TEXT("Hero");

// Composite (value type)
FVector Position(0.f, 1.f, 0.f);

// User-defined enum
UENUM()
enum class EDirection : uint8 { Up, Down, Left, Right };
EDirection Facing = EDirection::Up;

int32 WholeSpeed = static_cast<int32>(Speed); // explicit, 5

Unreal Blueprint

Variable types are chosen from a dropdown in the My Blueprint panel (Boolean, Integer, Float, Vector, a user-defined Enum asset, or another Blueprint class as a reference type). An implicit widening conversion (Integer → Float) is a straight wire; a narrowing conversion needs an explicit conversion node, visually mirroring the cast above.

Practical Example

An inventory slot needs a stack count that can never exceed 999 and never go negative — a small, bounded value, which argues for a small integer type rather than the default int:

csharp
public class InventorySlot
{
    public const int MaxStack = 999;
    private ushort count; // unsigned short: 0..65535, plenty for 0..999

    public void Add(int amount)
    {
        int result = count + amount;
        count = (ushort)Mathf.Clamp(result, 0, MaxStack);
    }
}

Choosing ushort over int here is deliberate: it documents "this value is never negative and never large" directly in the type, and the cast back to ushort after clamping makes the narrowing conversion visible rather than implicit.

Common Mistakes

  • Integer division truncation. 7 / 2 in C# or C++ evaluates to 3, not 3.5, because both operands are int — the result is an int, and the fractional part is silently discarded. At least one operand must be a floating-point type (7 / 2.0f) to get a fractional result.
  • Overflow. Adding to an int past its maximum value wraps around to a large negative number (or throws, depending on build configuration) — it does not "become a bigger number." A running total that can plausibly exceed ~2.1 billion needs a long, not an int.
  • Using float for money or exact counts. Floating point cannot represent every decimal value exactly, so repeated arithmetic accumulates small errors. Anything requiring exact equality checks (currency, exact match conditions) should avoid float comparisons via == and use integer types or an explicit tolerance instead.
  • Assuming a struct behaves like a class. Passing a large struct by value copies the whole thing on every function call — surprising both in behavior (mutating the copy doesn't mutate the original) and in performance (large structs should be passed by reference where the language allows it).
  • Comparing floating-point values for exact equality. a == b on two floats that were computed via different paths can be false even when they are "the same" mathematically, due to rounding. Compare with a small epsilon tolerance instead (Mathf.Approximately in Unity).

Best Practices

  • Pick the smallest type that can safely hold every value the variable will ever need — not the largest "to be safe."
  • Use enum instead of raw integers or strings for a closed set of named options; it is self-documenting and lets the compiler catch invalid values.
  • Make narrowing conversions explicit even when the language would allow an implicit one, so data loss is visible at the call site.
  • Prefer struct for small, short-lived, logically-value-like data (a 2D coordinate); prefer class when identity and shared mutation matter (an enemy, a save-game record).
  • Never compare floating-point values with ==; use an epsilon-based comparison.

Exercises

  1. Declare a variable to hold a player's remaining lives (0-9). Justify the type you chose over the alternatives.
  2. Predict, then verify, the result of int result = 5 / 2; versus float result = 5 / 2f; and explain the difference.
  3. Write an enum for a traffic light (Red, Yellow, Green) and a function that returns the next state given the current one.
  4. Find a place in a sample project where a float is compared with == and rewrite it using an epsilon comparison.
  • Variables — data types are the "shape" half of every variable declaration.
  • Operators — which operators are valid, and what they produce, depends on the operands' data types.

References

Related Concepts