ResourcesComputer ScienceVariables & Types
Computer ScienceCollege

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

// Click Play or Step Forward to begin

Stack

Empty

Heap

Empty

Step through to see how primitives and objects are allocated in memory.
Step 0 / 5
Primitives (int, float) live on the stack with independent copies. Objects live on the heap — references on the stack point to them.

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).

In Practice

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.

Type system taxonomy diagram showing static vs dynamic, strong vs weak typing

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.

Compiler type checking process diagram

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)

Integer representation diagram showing unsigned and two's complement encoding

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).

Precision Warning

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
Stack and heap memory layout visualization

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

Type conversion and casting flowchart

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 (intInteger in Java). Unboxing extracts the primitive back. Both incur performance overhead from heap allocation. Unboxing a null reference throws NullPointerException.

7Code Walkthroughs

Introductory

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.

Time: O(1)Space: O(1)

Key insight: Declaration allocates memory; assignment stores a value in that location.

Intermediate

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.

Time: O(1)Space: O(1)

Key insight: Value types are copied on assignment; reference types share the same memory address.

Intermediate

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.30000000000000004

Line 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.

Time: O(1)Space: O(1)

Key insight: Implicit conversions are safe (widening); explicit casts may lose data (narrowing).

Advanced

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] → None

Line 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.

Time: O(1) per nodeSpace: O(n)

Key insight: Local primitives live on stack; objects live on heap; references (pointers) connect them.

8Memory Aids

Stack vs Heap

“Stack = cafeteria trays (LIFO, fast, small). Heap = warehouse shelves (any order, slow, huge).”

Value vs Reference

“Value type = photocopy (independent copy). Reference type = shared Google Doc (same document, multiple editors).”

Two's Complement

“Flip all bits, add one” — the recipe for negation in binary.

Widening vs Narrowing

“Widening = pouring a cup into a bucket (safe). Narrowing = pouring a bucket into a cup (overflow).”

Static vs Dynamic

“Static = dress code enforced at the door (compile-time). Dynamic = wear whatever, we'll check inside (runtime).”

IEEE 754 Gotcha

“0.1 + 0.2 ≠ 0.3 — never compare floats with ==, use an epsilon threshold.”

9Common Mistakes

Comparing Floats with ==

if (0.1 + 0.2 == 0.3) → false

✅ Use an epsilon: if abs(a - b) < 1e-9. Floating-point arithmetic has inherent precision limits.

Confusing Assignment with Declaration

❌ 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.

Ignoring Integer Overflow

❌ 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.

Modifying Shared References Unintentionally

❌ 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.

Confusing Amortized with Worst-Case

❌ “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.

Dereferencing Null / Dangling Pointers

❌ 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.

Truncation in Narrowing Casts

int(9.99) → expecting 10 (rounding)

int() truncates toward zero, giving 9. Use round() for rounding. Always be explicit about truncation vs rounding behavior.

Assuming Strong = Static

❌ “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.

Related Topics