Introduction to Data Types
Data types are fundamental concepts in programming that define the type of data a variable can hold and the operations that can be performed on it. Understanding data types is crucial for writing correct, efficient, and bug-free code. Different programming languages approach data typing differently, but the core concepts remain consistent across languages.
Key Concepts
- Type Safety: Prevents operations on incompatible data types
- Static vs Dynamic: When types are checked (compile-time vs runtime)
- Strong vs Weak: How strictly types are enforced
- Primitive vs Complex: Built-in types vs user-defined types
- Implicit vs Explicit: Automatic vs manual type conversion
1. Classification of Data Types
By Mutability
# Immutable types (cannot be changed after creation)
number = 42 # int - immutable
text = "hello" # str - immutable
tuple_data = (1, 2) # tuple - immutable
# Modifying immutable types creates new objects
text = text + " world" # New string created
# Mutable types (can be changed in-place)
list_data = [1, 2, 3] # list - mutable
dict_data = {"a": 1} # dict - mutable
set_data = {1, 2, 3} # set - mutable
list_data.append(4) # Modifies original list
// JavaScript mutability
// Primitives are immutable
let str = "hello";
str.toUpperCase(); // Returns new string, doesn't modify original
// Objects are mutable
let obj = { name: "Alice" };
obj.name = "Bob"; // Modifies original object
let arr = [1, 2, 3];
arr.push(4); // Modifies original array
By Storage
// Stack vs Heap allocation
#include <stdio.h>
#include <stdlib.h>
int main() {
// Stack allocation (fixed size, automatic)
int stack_int = 42; // 4 bytes on stack
char stack_char = 'A'; // 1 byte on stack
int stack_array[10]; // 40 bytes on stack
// Heap allocation (dynamic, manual)
int* heap_int = malloc(sizeof(int));
*heap_int = 42; // 4 bytes on heap
char* heap_string = malloc(100); // 100 bytes on heap
free(heap_int); // Must free heap memory
free(heap_string);
return 0;
}
2. Primitive Data Types
Numeric Types
# Python numeric types integer = 42 # int (unlimited precision) float_num = 3.14159 # float (double precision) complex_num = 2 + 3j # complex # Numeric operations print(10 // 3) # 3 (integer division) print(10 / 3) # 3.33333 (float division) print(10 % 3) # 1 (modulo) print(2 ** 3) # 8 (exponentiation) print(abs(-5)) # 5 (absolute value) print(round(3.14159, 2)) # 3.14 (rounding)
// JavaScript numeric types let integer = 42; // Number (64-bit floating point) let float = 3.14159; // Number let bigInt = 9007199254740991n; // BigInt (arbitrary precision) let infinity = Infinity; // Infinity let nan = NaN; // Not a Number // Number limitations console.log(0.1 + 0.2); // 0.30000000000000004 (precision issue) console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
// C numeric types
#include <stdint.h>
#include <float.h>
int main() {
// Integer types
char c = 127; // 1 byte (-128 to 127)
unsigned char uc = 255; // 1 byte (0 to 255)
short s = 32767; // 2 bytes (-32,768 to 32,767)
int i = 2147483647; // 4 bytes (-2^31 to 2^31-1)
long l = 9223372036854775807; // 8 bytes (on 64-bit)
// Fixed-size integers (C99)
int8_t i8 = 127;
uint16_t u16 = 65535;
int32_t i32 = 2147483647;
uint64_t u64 = 18446744073709551615ULL;
// Floating point
float f = 3.14159f; // 4 bytes, ~7 decimal digits
double d = 3.141592653589793; // 8 bytes, ~15 decimal digits
return 0;
}
Boolean Type
# Python boolean
is_active = True
is_deleted = False
# Boolean operations
print(True and False) # False
print(True or False) # True
print(not True) # False
# Truthy/Falsy values
print(bool(0)) # False
print(bool(1)) # True
print(bool("")) # False
print(bool("hello")) # True
print(bool([])) # False
print(bool([1, 2])) # True
// JavaScript boolean
let isActive = true;
let isDeleted = false;
// Truthy/Falsy
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true (object)
console.log(Boolean({})); // true
Character Type
// C character types
#include <stdio.h>
#include <wchar.h>
int main() {
// ASCII characters
char ch = 'A';
char newline = '\n';
char tab = '\t';
// Escape sequences
char quote = '\'';
char backslash = '\\';
char hex = '\x41'; // 'A' in hex
// Wide characters (Unicode)
wchar_t wch = L'世';
wprintf(L"%lc\n", wch);
return 0;
}
String Type
# Python strings
single = 'Hello'
double = "World"
multi_line = """This is
a multi-line
string"""
# String operations
name = "Alice"
greeting = f"Hello, {name}!" # f-string
length = len(name) # 5
uppercase = name.upper() # "ALICE"
lowercase = name.lower() # "alice"
parts = "a,b,c".split(",") # ["a", "b", "c"]
joined = "-".join(["a", "b", "c"]) # "a-b-c"
// JavaScript strings
let single = 'Hello';
let double = "World";
let template = `Hello, ${name}!`; // Template literal
let multiline = `This is
a multi-line
string`;
// String methods
let str = "Hello World";
console.log(str.length); // 11
console.log(str.toUpperCase()); // "HELLO WORLD"
console.log(str.includes("World")); // true
console.log(str.slice(0, 5)); // "Hello"
console.log(str.split(" ")); // ["Hello", "World"]
3. Composite Data Types
Arrays/Lists
# Python lists (dynamic arrays) numbers = [1, 2, 3, 4, 5] mixed = [1, "hello", 3.14, True] # List operations numbers.append(6) # Add to end numbers.insert(0, 0) # Insert at position numbers.extend([7, 8]) # Extend with list popped = numbers.pop() # Remove last numbers.remove(3) # Remove first occurrence index = numbers.index(5) # Find index numbers.sort() # Sort in-place reversed = numbers[::-1] # Reverse # List comprehension squares = [x**2 for x in range(10)] evens = [x for x in range(20) if x % 2 == 0]
// JavaScript arrays let numbers = [1, 2, 3, 4, 5]; let mixed = [1, "hello", true, null]; // Array operations numbers.push(6); // Add to end numbers.unshift(0); // Add to beginning let last = numbers.pop(); // Remove last let first = numbers.shift(); // Remove first numbers.splice(2, 1); // Remove at index let sliced = numbers.slice(1, 3); let doubled = numbers.map(n => n * 2); let evens = numbers.filter(n => n % 2 === 0); let sum = numbers.reduce((a, b) => a + b, 0);
Tuples
# Python tuples (immutable sequences) point = (10, 20) coordinates = (x, y, z) = (1, 2, 3) single_element = (42,) # Trailing comma required # Tuple operations print(point[0]) # Access by index print(len(point)) # Length print(point + (30,)) # Concatenation print(point * 2) # Repetition print(10 in point) # Membership
// TypeScript tuples (fixed length, typed) let point: [number, number] = [10, 20]; let user: [string, number, boolean] = ["Alice", 30, true]; // Accessing tuple elements console.log(point[0]); // 10 console.log(user[1]); // 30 // Tuple with optional elements let optional: [string, number?] = ["Hello"]; // Valid
Dictionaries/Maps/Objects
# Python dictionaries
user = {
"name": "Alice",
"age": 30,
"email": "[email protected]"
}
# Dictionary operations
user["city"] = "New York" # Add/update
name = user.get("name") # Safe access
age = user.pop("age") # Remove and return
del user["email"] # Delete key
# Dictionary iteration
for key in user:
print(f"{key}: {user[key]}")
for key, value in user.items():
print(f"{key}: {value}")
# Dictionary comprehension
squares = {x: x**2 for x in range(5)}
// JavaScript objects
let user = {
name: "Alice",
age: 30,
email: "[email protected]"
};
// Access and modification
user.city = "New York"; // Dot notation
user["phone"] = "123-4567"; // Bracket notation
delete user.email; // Delete property
// Object iteration
for (let key in user) {
console.log(`${key}: ${user[key]}`);
}
Object.keys(user).forEach(key => {
console.log(`${key}: ${user[key]}`);
});
// Map object (preserves insertion order)
let map = new Map();
map.set("name", "Alice");
map.set("age", 30);
map.get("name"); // "Alice"
Sets
# Python sets (unordered, unique elements)
numbers = {1, 2, 3, 4, 5}
mixed = {1, "hello", 3.14}
# Set operations
numbers.add(6) # Add element
numbers.remove(3) # Remove element
numbers.discard(10) # Remove if exists
popped = numbers.pop() # Remove arbitrary
# Set operations with other sets
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
union = set_a | set_b # {1, 2, 3, 4, 5, 6}
intersection = set_a & set_b # {3, 4}
difference = set_a - set_b # {1, 2}
symmetric_diff = set_a ^ set_b # {1, 2, 5, 6}
// JavaScript Set let numbers = new Set([1, 2, 3, 4, 5]); numbers.add(6); numbers.delete(3); numbers.has(4); // true numbers.size; // 5 // Set operations (need to implement) let setA = new Set([1, 2, 3, 4]); let setB = new Set([3, 4, 5, 6]); let union = new Set([...setA, ...setB]); let intersection = new Set([...setA].filter(x => setB.has(x))); let difference = new Set([...setA].filter(x => !setB.has(x)));
Structs/Records
// C structs
#include <stdio.h>
#include <string.h>
struct Person {
char name[50];
int age;
float height;
};
struct Point {
int x;
int y;
};
int main() {
struct Person person1 = {"Alice", 30, 5.7};
struct Person person2;
strcpy(person2.name, "Bob");
person2.age = 25;
person2.height = 6.0;
printf("Name: %s, Age: %d\n", person1.name, person1.age);
return 0;
}
// Rust structs
struct Person {
name: String,
age: u32,
height: f64,
}
struct Point(i32, i32); // Tuple struct
struct Unit; // Unit struct
fn main() {
let person = Person {
name: String::from("Alice"),
age: 30,
height: 5.7,
};
let point = Point(10, 20);
println!("Name: {}, Age: {}", person.name, person.age);
println!("Point: ({}, {})", point.0, point.1);
}
4. Type Systems
Static Typing
// Java - statically typed
public class StaticTyping {
public static void main(String[] args) {
int number = 42; // Type declared
String text = "Hello"; // Type inferred? No, declared
number = 100; // OK - same type
// number = "text"; // Compilation error
// text = 42; // Compilation error
}
}
// TypeScript - statically typed with type inference
let number = 42; // Type inferred as number
let text: string = "Hello"; // Explicit type
let mixed: number | string = "can be string or number";
// Type checking
function add(a: number, b: number): number {
return a + b;
}
add(5, 3); // OK
// add("5", 3); // Type error
Dynamic Typing
# Python - dynamically typed
x = 42 # x is int
x = "hello" # x is now str - perfectly valid
x = [1, 2, 3] # x is now list
# Type checking at runtime
def add(a, b):
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a + b
elif isinstance(a, str) and isinstance(b, str):
return a + b
else:
raise TypeError("Unsupported types")
print(add(5, 3)) # 8
print(add("Hello", "World")) # HelloWorld
# print(add(5, "3")) # Raises TypeError
// JavaScript - dynamically typed
let x = 42; // number
x = "hello"; // string - valid
x = [1, 2, 3]; // array - valid
// Type coercion
console.log(5 + "3"); // "53" (string concatenation)
console.log(5 - "3"); // 2 (numeric subtraction)
console.log("5" * "3"); // 15 (numeric multiplication)
console.log(true + true); // 2 (true converted to 1)
Strong vs Weak Typing
# Python - strong typing # No implicit type conversion x = 5 y = "10" # print(x + y) # TypeError: unsupported operand type(s) # Explicit conversion required result = x + int(y) # 15
// JavaScript - weak typing (lots of implicit conversions)
console.log(5 + "3"); // "53" - number coerced to string
console.log("5" - 3); // 2 - string coerced to number
console.log(true + true); // 2 - boolean coerced to number
console.log([] + []); // "" - arrays coerced to strings
console.log([] + {}); // "[object Object]"
console.log({} + []); // 0 (in some cases)
5. Type Conversion
Implicit Conversion (Coercion)
// JavaScript implicit coercion
console.log("5" + 3); // "53"
console.log("5" - 3); // 2
console.log(true + true); // 2
console.log(false + "a"); // "falsea"
// Loose equality (==) performs coercion
console.log(5 == "5"); // true
console.log(false == 0); // true
console.log(null == undefined); // true
// Strict equality (===) doesn't coerce
console.log(5 === "5"); // false
console.log(false === 0); // false
Explicit Conversion (Casting)
# Python explicit conversion
x = 42
print(str(x)) # "42"
print(float(x)) # 42.0
print(hex(x)) # "0x2a"
print(bin(x)) # "0b101010"
y = "3.14"
print(int(float(y))) # 3
print(float(y)) # 3.14
z = "hello"
print(list(z)) # ['h', 'e', 'l', 'l', 'o']
print(tuple(z)) # ('h', 'e', 'l', 'l', 'o')
// JavaScript explicit conversion
let x = 42;
console.log(String(x)); // "42"
console.log(x.toString()); // "42"
console.log(Number("42")); // 42
console.log(parseInt("42px")); // 42
console.log(parseFloat("3.14px")); // 3.14
console.log(Boolean(0)); // false
let arr = [1, 2, 3];
console.log(JSON.stringify(arr)); // "[1,2,3]"
Type Checking
# Python type checking
x = 42
print(type(x)) # <class 'int'>
print(isinstance(x, int)) # True
print(isinstance(x, (int, float))) # True
def process(data):
if isinstance(data, str):
return data.upper()
elif isinstance(data, list):
return [str(x) for x in data]
elif isinstance(data, dict):
return {k: str(v) for k, v in data.items()}
else:
return str(data)
// JavaScript type checking let x = 42; console.log(typeof x); // "number" console.log(Array.isArray([])); // true console.log(x instanceof Number); // false (primitive) console.log(null === null); // true console.log(typeof null); // "object" (JavaScript quirk)
6. Null and Undefined
Python
# Python - None
value = None
if value is None:
print("Value is None")
# None is a singleton
x = None
y = None
print(x is y) # True
# Default values
def greet(name=None):
if name is None:
print("Hello, World!")
else:
print(f"Hello, {name}!")
JavaScript
// JavaScript - null and undefined
let undefinedVar; // undefined
let nullVar = null; // null
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (quirk)
// Difference
console.log(undefined == null); // true (loose equality)
console.log(undefined === null); // false (strict equality)
// Optional chaining
const user = { name: "Alice" };
console.log(user?.address?.city); // undefined (no error)
C/C++
// C - NULL pointer
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = NULL; // Null pointer
if (ptr == NULL) {
printf("Pointer is null\n");
}
// Dereferencing NULL causes crash
// *ptr = 42; // Segmentation fault
return 0;
}
7. Custom Types
Enums
# Python enums
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Status(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
# Usage
color = Color.RED
print(color.name) # "RED"
print(color.value) # 1
if color == Color.RED:
print("It's red!")
// JavaScript enums (using objects)
const Color = {
RED: Symbol('red'),
GREEN: Symbol('green'),
BLUE: Symbol('blue')
};
// Or with strings
const Status = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected'
} as const;
type StatusType = typeof Status[keyof typeof Status];
// Rust enums
enum Color {
Red,
Green,
Blue,
RGB(u8, u8, u8), // Enum with data
}
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Type Aliases
# Python type aliases (type hints) from typing import List, Dict, Union UserId = int UserMap = Dict[UserId, str] def get_user_name(user_id: UserId, users: UserMap) -> str: return users.get(user_id, "Unknown")
// TypeScript type aliases
type UserId = number;
type User = {
id: UserId;
name: string;
email: string;
};
type ApiResponse<T> = {
data: T;
status: number;
error?: string;
};
// Union types
type Status = "pending" | "approved" | "rejected";
type ID = string | number;
Classes
# Python classes
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def greet(self) -> str:
return f"Hello, I'm {self.name}"
def is_adult(self) -> bool:
return self.age >= 18
# Inheritance
class Student(Person):
def __init__(self, name: str, age: int, student_id: str):
super().__init__(name, age)
self.student_id = student_id
// JavaScript classes
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
isAdult() {
return this.age >= 18;
}
}
class Student extends Person {
constructor(name, age, studentId) {
super(name, age);
this.studentId = studentId;
}
}
8. Type Inference
# Python type hints (type inference in modern IDEs) def add(a: int, b: int) -> int: return a + b # Type inference tools (mypy) x = 42 # mypy infers int y = "hello" # mypy infers str z = [1, 2, 3] # mypy infers List[int]
// TypeScript type inference
let x = 42; // inferred as number
let y = "hello"; // inferred as string
let z = [1, 2, 3]; // inferred as number[]
// Contextual typing
window.onmousedown = function(event) {
// event is inferred as MouseEvent
console.log(event.button);
};
// Return type inference
function add(a: number, b: number) {
return a + b; // inferred as number
}
// Rust type inference let x = 42; // i32 (default integer type) let y = 3.14; // f64 (default float type) let v = vec![1, 2]; // Vec<i32> // Type annotations needed when ambiguous let numbers: Vec<i32> = Vec::new(); // Must specify type let result: u32 = "42".parse().unwrap(); // Type annotation required
9. Language-Specific Type Features
Python - Duck Typing
# Python duck typing - "If it walks like a duck..." class Duck: def quack(self): return "Quack!" class Person: def quack(self): return "I'm quacking like a duck!" def make_it_quack(obj): return obj.quack() # Works with any object that has quack() duck = Duck() person = Person() print(make_it_quack(duck)) # "Quack!" print(make_it_quack(person)) # "I'm quacking like a duck!"
TypeScript - Structural Typing
// TypeScript structural typing
interface Point {
x: number;
y: number;
}
interface NamedPoint {
x: number;
y: number;
name: string;
}
// NamedPoint is compatible with Point (has at least x and y)
function printPoint(p: Point) {
console.log(`(${p.x}, ${p.y})`);
}
const np: NamedPoint = { x: 10, y: 20, name: "Origin" };
printPoint(np); // Works! (structural compatibility)
Rust - Ownership System
// Rust ownership and borrowing
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2, s1 is no longer valid
// println!("{}", s1); // Error: value borrowed after move
// Borrowing
let s3 = String::from("world");
let len = calculate_length(&s3); // Borrow reference
println!("Length of '{}' is {}", s3, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but nothing is dropped
C - Union Types
// C unions - same memory location for different types
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 42;
printf("data.i: %d\n", data.i); // 42
printf("data.f: %f\n", data.f); // Undefined
data.f = 3.14;
printf("data.i: %d\n", data.i); // Undefined
printf("data.f: %f\n", data.f); // 3.14
return 0;
}
10. Common Pitfalls
Floating-Point Precision
# Floating-point precision issues
print(0.1 + 0.2) # 0.30000000000000004 (not 0.3)
# Solution: use decimal
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.3
# Or use rounding
print(round(0.1 + 0.2, 2)) # 0.3
// JavaScript floating-point issues console.log(0.1 + 0.2); // 0.30000000000000004 // Solution: use epsilon const epsilon = 0.000001; console.log(Math.abs((0.1 + 0.2) - 0.3) < epsilon); // true
Integer Overflow
// C integer overflow (undefined behavior)
#include <stdio.h>
#include <limits.h>
int main() {
int x = INT_MAX; // 2,147,483,647
int y = x + 1; // Undefined behavior (wraps to -2,147,483,648)
printf("%d\n", y); // -2147483648
// Use unsigned for wrap-around
unsigned int ux = UINT_MAX;
unsigned int uy = ux + 1; // Wraps to 0
printf("%u\n", uy); // 0
return 0;
}
Reference vs Value Types
// JavaScript: primitives vs objects
// Primitives (passed by value)
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 (unchanged)
// Objects (passed by reference)
let obj1 = { value: 5 };
let obj2 = obj1;
obj2.value = 10;
console.log(obj1.value); // 10 (changed)
// Copying objects
let obj3 = { ...obj1 }; // Shallow copy
let obj4 = JSON.parse(JSON.stringify(obj1)); // Deep copy
# Python: mutable vs immutable # Immutable a = 5 b = a b = 10 print(a) # 5 # Mutable list1 = [1, 2, 3] list2 = list1 list2.append(4) print(list1) # [1, 2, 3, 4] (changed) # Copying list3 = list1.copy() # Shallow copy import copy list4 = copy.deepcopy(list1) # Deep copy
Conclusion
Data types are fundamental to programming:
Key Takeaways
- Type Systems: Understand static vs dynamic, strong vs weak typing
- Primitive Types: Numbers, strings, booleans - building blocks
- Composite Types: Arrays, objects, structs - organizing data
- Type Safety: Prevents many common programming errors
- Type Conversion: Explicit is better than implicit
- Language-Specific: Each language has unique type features
Type System Comparison
| Feature | Python | JavaScript | TypeScript | Rust | C/C++ |
|---|---|---|---|---|---|
| Typing | Dynamic | Dynamic | Static | Static | Static |
| Strength | Strong | Weak | Strong | Strong | Weak |
| Type Inference | Limited | Limited | Strong | Strong | Limited |
| Null Safety | Partial | No | Yes | Yes | No |
| Generics | Yes | No | Yes | Yes | Yes |
| Memory Safety | Yes | Yes | Yes | Yes | No |
Best Practices
- Be explicit with type conversions
- Use type hints/annotations when available
- Understand your language's type system
- Test edge cases (null, undefined, empty collections)
- Choose appropriate types for your data
- Be aware of precision issues with floating-point
- Consider mutability implications
Data types are the foundation of how programs store and manipulate information. Mastering them is essential for writing correct, efficient, and maintainable code!