Variables & Types
Variables and types are two of the most fundamental abstractions in computer science. A variable is a named storage location; a type classifies the values it can hold, the operations permitted, and its internal memory representation.
This guide covers type systems (static vs dynamic, strong vs weak), primitive representations (integers, floats, characters), reference types and memory management (stack vs heap), type conversion, code walkthroughs, and a 10-question practice quiz.
Memory Layout: Stack vs Heap
Stack
Empty
Heap
Empty
1Introduction
In the realm of programming languages and computation, variables and types are two of the most fundamental abstractions. A variable can be formally defined as a named storage location in a computer's memory that holds a value. A type is a classification that determines the kind of value a variable can hold, the set of permissible operations, and its internal representation in memory.
The study of variables and types is central to understanding how programs manipulate data, how compilers translate source code, and how runtime systems manage memory. It underpins Programming Languages (syntax, semantics, type systems), Compilers (type checking, code generation), Operating Systems (memory management), and Software Engineering (design for correctness).
Modern IDEs heavily leverage type information for autocompletion, refactoring, and static analysis. In large-scale enterprise applications (Java, C#, TypeScript), a robust type system prevents entire classes of errors before the code even runs, significantly reducing debugging time and improving reliability.

2Key Definitions
Essential terms for understanding variables and types at the university level.
Variable
Named storage location in memory that holds a value
Type
Classification specifying values, operations, and memory representation
Static Typing
Type checking at compile-time (C++, Java, Rust)
Dynamic Typing
Type checking at runtime; types bound to values, not variables (Python, JS)
Strong Typing
Restricts implicit coercion; errors on incompatible ops (Python, Java)
Weak Typing
Allows implicit conversions between types (JavaScript, C)
Primitive Type
Basic built-in type: int, float, char, boolean
Reference Type
Value is a reference (address) to data on the heap
Pointer
Variable storing a memory address; supports arithmetic (C/C++)
Type Coercion
Automatic implicit conversion by the compiler/runtime
Type Casting
Explicit conversion by the programmer; may lose data
Type Inference
Compiler deduces types automatically (var, auto, let)
Subtype
S <: T — a value of S can be used wherever T is expected
Boxing / Unboxing
Wrapping a primitive in an object (boxing) and unwrapping it (unboxing)
Garbage Collection
Automatic reclamation of unreachable heap memory (Java, Python, Go)
Dangling Pointer
Pointer to freed memory — dereferencing causes undefined behavior
3Type Systems and Type Theory
Type systems are formal mechanisms that classify expressions and values within a programming language, enforcing rules to ensure program correctness and safety.
Static vs. Dynamic Typing
Static Typing
Type checking at compile-time. The type of every variable is known before execution.
- Early error detection
- Better performance (no runtime checks)
- Robust IDE support (autocompletion, refactoring)
- Examples: C++, Java, Rust, Go
Dynamic Typing
Type checking at runtime. Variables can hold values of any type and change type.
- Rapid prototyping and flexibility
- Less boilerplate code
- Late error detection (runtime crashes)
- Examples: Python, JavaScript, Ruby
Strong vs. Weak Typing
Strong Typing
Restricts implicit conversions. 1 + "2" raises a TypeError in Python.
Weak Typing
Allows implicit coercion. 1 + "2" evaluates to "12" in JavaScript.
Nominal vs. Structural Typing
Nominal typing determines compatibility by declared type name — two classes with identical fields are distinct unless explicitly related. Structural typing determines compatibility by shape — if types have the same members, they're compatible regardless of name (TypeScript uses this).
Type Soundness
A sound type system guarantees well-typed programs can't “go wrong” at runtime. The Type Safety Theorem (Milner) has two parts: Progress (a well-typed expression can always take a step or is a value) and Preservation (evaluation preserves types). Languages like ML, Haskell, and Rust provide strong soundness guarantees.

4Primitive Types and Representations
Integer Representations
Unsigned integers use all bits for magnitude: an N-bit value ranges from 0 to 2N−1. Two's complement is the standard signed representation: MSB indicates sign, range is [−2N−1, 2N−1−1].
Two's Complement: Getting −5 from 5 (8-bit)
5 in binary: 00000101
Invert bits: 11111010 (one's complement)
Add 1: 11111011 (−5 in two's complement)

Floating-Point: IEEE 754
Floating-point numbers are represented as (−1)S × M × 2E, where S is the sign bit, M the mantissa, and E the exponent. Single-precision uses 32 bits (1+8+23), double uses 64 bits (1+11+52).
0.1 + 0.2 ≠ 0.3 in floating-point. Many decimal fractions have infinite binary representations, causing rounding errors. Use fixed-point or arbitrary-precision for financial calculations.
Character Encoding
ASCII encodes 128 characters in 7 bits. Unicode assigns unique code points to every character in every writing system. UTF-8 is a variable-width encoding (1–4 bytes) backward-compatible with ASCII and dominant on the web.
5Reference Types and Memory Management
Stack vs. Heap Allocation
Stack
- LIFO allocation (push/pop)
- Local variables, function params
- Very fast — simple pointer manipulation
- Auto-deallocated when scope ends
- Limited size (stack overflow risk)
Heap
- Dynamic allocation (new, malloc)
- Objects, large data structures
- Slower — complex allocator algorithms
- Manual free or garbage collection
- Much larger capacity

Garbage Collection vs. Manual Management
Manual management (C/C++) gives fine-grained control but risks memory leaks and dangling pointers. Garbage collection (Java, Python, Go) automatically reclaims unreachable memory — safer but introduces GC pauses and overhead. Smart pointers (C++ unique_ptr, shared_ptr) and ARC (Swift) provide middle-ground approaches.
Memory Leaks and Dangling Pointers
Memory Leak
Allocated memory never freed — gradual consumption until crash. Caused by forgetting delete / free().
Dangling Pointer
Pointer to freed memory — dereferencing causes undefined behavior, crashes, or security vulnerabilities.
6Type Conversion and Coercion

Widening vs. Narrowing
Widening (Safe)
Smaller type → larger type. No data loss.
int → float → double
Often implicit (coercion)
Narrowing (Risky)
Larger type → smaller type. Possible data loss.
double → int (truncation)
Requires explicit cast
Boxing and Unboxing
Boxing wraps a primitive in an object (int → Integer in Java). Unboxing extracts the primitive back. Both incur performance overhead from heap allocation. Unboxing a null reference throws NullPointerException.
7Code Walkthroughs
Variable Declaration and Initialization
# Declaration with type annotation age: int = 25 name: str = "Alice" # Assignment (different from declaration) age = 26 # Now age is 26 # Multiple declaration x, y, z = 1, 2, 3
Line 2: Declares age of type int and initializes to 25.
Line 6: Assignment changes the stored value from 25 to 26.
Line 9: Multiple assignment unpacks three values simultaneously.
Key insight: Declaration allocates memory; assignment stores a value in that location.
Reference vs Value Types
def modify_value(x):
x = x + 10
return x
def modify_reference(lst):
lst.append(4)
lst = [99, 88] # New local reference
# Value type
num = 5
modify_value(num)
print(num) # Still 5
# Reference type
arr = [1, 2, 3]
modify_reference(arr)
print(arr) # [1, 2, 3, 4]Lines 1–3: Function receives a copy of the value; changes are local only.
Line 12: num remains 5 — integers are value types, the function worked on a copy.
Line 17: arr becomes [1,2,3,4] — the function modified the original list object.
Key insight: Value types are copied on assignment; reference types share the same memory address.
Type Casting and Coercion
# Implicit coercion (widening)
int_val = 10
float_val = int_val # int → float, safe
# Explicit narrowing (data loss)
double_val = 9.99
int_from_double = int(double_val) # Truncates to 9
# Floating-point precision issue
result = 0.1 + 0.2 # Not exactly 0.3!
print(f"Result: {result}") # 0.30000000000000004Line 3: Implicit widening — int promotes to float automatically (no data loss).
Line 7: Explicit narrowing — int() truncates decimal, losing precision.
Line 10: Binary floating-point cannot exactly represent 0.1 or 0.2.
Key insight: Implicit conversions are safe (widening); explicit casts may lose data (narrowing).
Memory Layout: Stack vs Heap
class Node:
def __init__(self, val):
self.value = val
self.next = None
def create_linked_list():
head = None # Stack reference
node1 = Node(10) # Heap allocation
node2 = Node(20) # Heap allocation
node3 = Node(30) # Heap allocation
node1.next = node2
node2.next = node3
head = node1
return head
# head → [10|next] → [20|next] → [30|next] → NoneLine 7: head is a stack variable initially pointing to None.
Lines 8–10: Each Node() allocates memory on the heap.
Lines 11–12: Setting .next creates references between heap objects.
Key insight: Local primitives live on stack; objects live on heap; references (pointers) connect them.
8Memory Aids
“Stack = cafeteria trays (LIFO, fast, small). Heap = warehouse shelves (any order, slow, huge).”
“Value type = photocopy (independent copy). Reference type = shared Google Doc (same document, multiple editors).”
“Flip all bits, add one” — the recipe for negation in binary.
“Widening = pouring a cup into a bucket (safe). Narrowing = pouring a bucket into a cup (overflow).”
“Static = dress code enforced at the door (compile-time). Dynamic = wear whatever, we'll check inside (runtime).”
“0.1 + 0.2 ≠ 0.3 — never compare floats with ==, use an epsilon threshold.”
9Common Mistakes
❌ if (0.1 + 0.2 == 0.3) → false
✅ Use an epsilon: if abs(a - b) < 1e-9. Floating-point arithmetic has inherent precision limits.
❌ Thinking x = 5 always creates a new variable
✅ Declaration introduces a new variable into scope. Assignment changes the value of an existing variable. In Python, x = 5 in a function creates a local (shadowing), not modifying the global.
❌ Assuming 127 + 1 = 128 for an 8-bit signed int
✅ In two's complement, 127 + 1 = −128. Always check bounds. In C/C++, signed overflow is undefined behavior. Use wider types or explicit checks.
❌ Passing a list to a function and expecting the original to be unchanged
✅ Reference types share the same object. If a function appends to a list, the caller's list changes too. Use copy() or deepcopy() to avoid this.
❌ “Dynamic array append is O(n) because it sometimes resizes”
✅ Resizing happens rarely. The amortized cost per append is O(1). Amortized analysis spreads occasional expensive operations over many cheap ones.
❌ Using a pointer after free(ptr) or without checking for null
✅ Always set pointers to null after freeing. Use smart pointers in C++. In Java/C#, check for null before dereferencing or use Optional types.
❌ int(9.99) → expecting 10 (rounding)
✅ int() truncates toward zero, giving 9. Use round() for rounding. Always be explicit about truncation vs rounding behavior.
❌ “Python is dynamically typed, so it must be weakly typed”
✅ These are orthogonal axes. Python is dynamic AND strong. C is static AND weak. JavaScript is dynamic AND weak. The two concepts are independent.
Frequently Asked Questions
- What is the difference between static typing and dynamic typing?
- Static typing checks types at compile-time — every variable's type is known before the program runs (e.g., Java, C++, Rust). Dynamic typing checks types at runtime — variables can hold values of any type and may change type during execution (e.g., Python, JavaScript). Static typing catches more errors early; dynamic typing offers flexibility.
- Why might 0.1 + 0.2 not equal 0.3 in a program?
- Binary floating-point (IEEE 754) cannot exactly represent certain decimal fractions like 0.1 or 0.2. They become repeating binary fractions, causing small rounding errors. The sum 0.1 + 0.2 evaluates to approximately 0.30000000000000004. For exact decimal arithmetic, use specialized libraries (e.g., Python's Decimal module).
- What is the difference between a pointer and a reference?
- A pointer stores a memory address directly and can be null, reassigned, or used for arithmetic (C/C++). A reference is an alias for an existing variable — in C++ it must be initialized, cannot be null, and cannot be reassigned. Java/C# references are closer to "safe pointers" — nullable and reassignable but no arithmetic.
- What is two's complement and why is it used?
- Two's complement is the standard representation for signed integers. Positive numbers use normal binary. Negative numbers are formed by inverting all bits and adding 1. It's used because addition and subtraction work with the same hardware for both positive and negative numbers — no special subtraction circuit is needed.
- What is the difference between stack and heap memory?
- The stack stores local variables and function call data in LIFO order — allocation is fast and automatic. The heap stores dynamically allocated objects whose lifetime extends beyond a single function — allocation is slower and requires explicit management (manual free or garbage collection). Primitives typically live on the stack; objects on the heap.
- What is type inference?
- Type inference lets the compiler automatically deduce a variable's type from its value or usage, eliminating the need for explicit annotations. For example, in Rust: `let x = 5;` infers x as i32. In TypeScript: `const name = "Alice"` infers string. It provides the safety of static typing with less verbosity.
Practice Quiz
Test your understanding of variables and types — select the correct answer for each question.
1.What is the primary difference between static typing and dynamic typing?
2.In a language with pass-by-value semantics, what happens when you pass an object to a function?
3.What does the IEEE 754 standard define?
4.What is type coercion?
5.What is the result of adding two unsigned 8-bit integers: 200 + 80?
6.What is the key advantage of strong typing over weak typing?
7.In two's complement representation, what is the range of a 4-bit signed integer?
8.What is boxing in the context of type systems?
9.What is a dangling pointer?
10.Why might 0.1 + 0.2 != 0.3 in a program?
Study Tips
- Trace through memory: Sketch stack and heap diagrams for code snippets to build intuition for value vs reference semantics.
- Compare across languages: Try the same operations in Python, Java, and C++ to see how type systems differ in practice.
- Use a debugger: Step through code and inspect variable types, values, and memory addresses to verify your understanding.
- Test edge cases: Explore integer overflow, floating-point precision, and null references firsthand.