Data Types #

Every value in Python has a type — and the type determines what operations can be performed on that value. Python is strongly typed: there’s no implicit conversion between incompatible types. You can’t directly add a number to a string like you can in JavaScript. But Python is also dynamically typed: types are determined at runtime, not at compile time. Understanding data types well — including which are mutable and which are immutable, how conversion works, and the non-intuitive traps — is an essential foundation before writing more complex Python code.

The Python Data Type Map #

To make things easier to map out, here’s a visualization of Python’s built-in data type hierarchy, split into scalar types (single values) and collection types (groups of values), along with their mutability grouping:

flowchart TD
    Root["Python Data Types"] --> Scalar["Scalar Types (Single Value)"]
    Root --> Collection["Collection Types (Group of Values)"]

    subgraph Skalar ["Scalar Category"]
        Scalar --> int["int<br/>'(Integer)'"]
        Scalar --> float["float<br/>'(Decimal)'"]
        Scalar --> complex["complex<br/>'(Complex Number)'"]
        Scalar --> bool["bool<br/>'(Boolean)'"]
        Scalar --> NoneType["NoneType<br/>'(None)'"]
    end

    subgraph Koleksi ["Collection Category"]
        Collection --> Ordered["Ordered"]
        Collection --> Unordered["Unordered"]

        Ordered -->|Mutable| list["list<br/>'[1, 2, 3]'"]
        Ordered -->|Immutable| tuple["tuple<br/>'(1, 2, 3)'"]
        Ordered -->|Immutable| str["str<br/>'\"hello\"'"]

        Unordered -->|Mutable| dict["dict<br/>'{\"key\": \"value\"}'"]
        Unordered -->|Mutable| set["set<br/>'{1, 2, 3}'"]
        Unordered -->|Immutable| frozenset["frozenset<br/>'({1, 2, 3})'"]
    end

int — Integers #

Python’s int has no size limit — it can hold integers as large as memory allows. This differs from languages like Java that have int (32-bit) and long (64-bit).

# Integer literals in various bases
decimal     = 255          # base 10 (default)
binary      = 0b11111111   # base 2  → 255
octal       = 0o377        # base 8  → 255
hexadecimal = 0xFF         # base 16 → 255

print(decimal, binary, octal, hexadecimal)
# → 255 255 255 255

# Python supports unbounded integers
factorial_100 = 1
for i in range(1, 101):
    factorial_100 *= i
print(factorial_100)  # → a 158-digit number, no overflow!

# Division operations — note the differences
print(10 / 3)    # → 3.3333... (always a float, even when it divides evenly)
print(10 // 3)   # → 3         (floor division, result is int)
print(10 % 3)    # → 1         (remainder / modulo)
print(2 ** 10)   # → 1024      (exponentiation)

# Thousands separators for readability
population = 270_000_000
budget = 1_500_000_000
print(population)   # → 270000000

float — Decimal Numbers #

float in Python uses the IEEE 754 double precision (64-bit) representation. This gives about 15–17 digits of decimal precision, but it also brings consequences that often surprise beginners.

# Float literals
pi      = 3.14159
avogadro = 6.022e23    # scientific notation: 6.022 × 10²³
tiny    = 1.5e-10      # 1.5 × 10⁻¹⁰

print(avogadro)  # → 6.022e+23
print(tiny)      # → 1.5e-10

Float Precision Trap #

# ANTI-PATTERN: comparing floats directly
print(0.1 + 0.2)          # → 0.30000000000000004 (not 0.3!)
print(0.1 + 0.2 == 0.3)   # → False  ← the classic trap!

# Why? Because 0.1 and 0.2 can't be represented
# exactly in binary (like 1/3 in decimal)

# CORRECT: use math.isclose() for float comparison
import math
print(math.isclose(0.1 + 0.2, 0.3))        # → True
print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9))  # → True

# Or round() before comparing
print(round(0.1 + 0.2, 10) == round(0.3, 10))  # → True
# ANTI-PATTERN: using float for money/financial calculations
price = 19.99
tax = 0.11
total = price * (1 + tax)
print(total)  # → 22.18890000000000... (not precise for money!)

# CORRECT: use the decimal module for financial calculations
from decimal import Decimal, ROUND_HALF_UP

price = Decimal("19.99")
tax = Decimal("0.11")
total = price * (1 + tax)
print(total)  # → 22.1889

# Round to 2 decimal places
total_rounded = total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(total_rounded)  # → 22.19
Never use float for calculations involving money, finance, or values that need exact decimal precision. Use the decimal module from the Python stdlib — it’s purpose-built for this need.

str — Strings (Text) #

A string in Python is a sequence of Unicode characters that is immutable — once created, its value can’t be changed. Every string operation produces a new string.

# Ways to create strings
s1 = 'single quotes'
s2 = "double quotes"
s3 = """multi-line
string
with triple-quote"""
s4 = '''this also
works multi-line'''

# Raw strings — backslashes are not interpreted as escapes
windows_path = r"C:\Users\Budi\Documents"   # r = raw string
regex_pattern = r"\d+\.\d+"                 # useful for regex
print(windows_path)  # → C:\Users\Budi\Documents

# Bytes strings — for binary data
binary_data = b"hello"
print(type(binary_data))  # → <class 'bytes'>

Important String Operations #

text = "Selamat Datang di Python"

# Indexing and slicing
print(text[0])       # → S (first index)
print(text[-1])      # → n (last index)
print(text[0:7])     # → Selamat
print(text[8:])      # → Datang di Python
print(text[:7])      # → Selamat
print(text[::2])     # → SlmtDtn iPto (every 2 characters)
print(text[::-1])    # → nohtyP id gnataDtamlaleS (reversed)

# Frequently used string methods
print(text.upper())           # → SELAMAT DATANG DI PYTHON
print(text.lower())           # → selamat datang di python
print(text.split(" "))        # → ['Selamat', 'Datang', 'di', 'Python']
print(text.replace("Python", "Dunia"))  # → Selamat Datang di Dunia
print("  spasi  ".strip())    # → spasi
print(text.startswith("Sel")) # → True
print(text.endswith("on"))    # → True
print("Python" in text)       # → True
print(len(text))              # → 24

String Formatting #

name = "Budi"
score = 92.5
rank = 3

# f-strings (Python 3.6+) — the recommended way
print(f"Hello, {name}!")                      # → Hello, Budi!
print(f"Score: {score:.1f}")                  # → Score: 92.5
print(f"Score: {score:.0f}")                  # → Score: 93 (rounded)
print(f"Rank: {rank:02d}")                    # → Rank: 03
print(f"Value: {score!r}")                    # → Value: 92.5
print(f"{'left':<10}|{'center':^10}|{'right':>10}")
# → left      |  center  |     right

# ANTI-PATTERN: concatenation in a loop — very slow for long strings
word_list = ["Python", "is", "a", "great", "language"]
result = ""
for word in word_list:
    result += word + " "   # creates a new string every iteration

# CORRECT: use join()
result = " ".join(word_list)
print(result)  # → Python is a great language

bool — Booleans #

bool is a subclass of int in Python. True equals 1 and False equals 0 — this enables some concise tricks but is also a source of confusion.

print(True + True)    # → 2
print(True * 5)       # → 5
print(False + 1)      # → 1
print(isinstance(True, int))  # → True (bool is a subclass of int!)

# Truthy and falsy values
# All of the values below are considered False when used in a condition:
falsy_values = [
    False, 0, 0.0, 0j,     # zero numbers
    "", '', b"",            # empty strings
    [], (), {},  set(),     # empty collections
    None,                   # None
]

# Every other value is considered True
print(bool(42))       # → True
print(bool(-1))       # → True (even negatives are True!)
print(bool(""))       # → False
print(bool("0"))      # → True  (the string "0" is not zero!)
print(bool([]))       # → False
print(bool([0]))      # → True  (a list with 1 element, even zero)
# Using truthy/falsy idiomatically
name = ""

# ANTI-PATTERN: explicit comparison with an empty string
if name == "":
    print("name is empty")

# CORRECT: just evaluate directly
if not name:
    print("name is empty")

# Another example: checking an empty list
data = []
if not data:
    print("no data")

None — No Value #

None is the only value of the NoneType type. It’s used to represent the absence of a value, similar to null in other languages.

# Common uses of None
def find_user(user_id):
    # returns None if not found
    if user_id not in database:
        return None
    return database[user_id]

result = find_user(999)

# ANTI-PATTERN: comparing None with ==
if result == None:
    print("not found")

# CORRECT: always use 'is' or 'is not' for None
if result is None:
    print("not found")

if result is not None:
    print(f"found: {result}")
# None as a default parameter (a common Python pattern)
def add_to_list(value, target=None):
    # ANTI-PATTERN: using a list as the direct default
    # def add_to_list(value, target=[]):  ← DANGEROUS! the default list is shared across calls

    # CORRECT: use None as a sentinel, create a new list inside the function
    if target is None:
        target = []
    target.append(value)
    return target

print(add_to_list(1))    # → [1]
print(add_to_list(2))    # → [2]  (a new list, not [1, 2]!)

Collection Types #

list — Ordered, Mutable Collection #

# Creating lists
numbers = [1, 2, 3, 4, 5]
mixed = [42, "hello", 3.14, True, None]     # different types allowed
nested = [[1, 2], [3, 4], [5, 6]]           # lists inside lists
empty = []

# Basic operations
numbers.append(6)          # add at the end → [1,2,3,4,5,6]
numbers.insert(0, 0)       # add at index 0 → [0,1,2,3,4,5,6]
numbers.pop()              # remove & return the last element → 6
numbers.pop(0)             # remove & return the element at index 0 → 0
numbers.remove(3)          # remove the value 3 (first occurrence)
numbers.sort()             # sort in place
numbers.reverse()          # reverse order in place
print(len(numbers))        # → number of elements
print(3 in numbers)        # → True/False

tuple — Ordered, Immutable Collection #

# Creating tuples
coordinates = (10.5, -6.2)
rgb = (255, 128, 0)
single_element = (42,)       # the comma is required for a 1-element tuple!
empty = ()

# TRAP: a 1-element tuple without the comma
not_a_tuple = (42)        # this is a plain int, not a tuple
print(type(not_a_tuple))  # → <class 'int'>
print(type((42,)))        # → <class 'tuple'>

# Tuples are immutable — can't be changed
coordinates[0] = 99         # TypeError: 'tuple' object does not support item assignment

# When to use tuple vs list?
# Tuple: data that shouldn't change (coordinates, RGB, database records)
# List: data that will be modified (shopping cart, processing queue)

dict — Key-Value Pairs #

# Creating a dict
user = {
    "name": "Budi Santoso",
    "age": 28,
    "email": "[email protected]",
    "active": True,
}

# Accessing values
print(user["name"])              # → Budi Santoso
print(user.get("phone"))         # → None (no error if the key doesn't exist)
print(user.get("phone", "-"))    # → - (default value)

# ANTI-PATTERN: direct access without checking
phone = user["phone"]            # KeyError if the key doesn't exist

# CORRECT: use .get() for keys that may not exist
phone = user.get("phone", "not available")

# Dict operations
user["phone"] = "081234567890"   # add/update a key
del user["active"]               # delete a key
print("email" in user)           # → True (check key existence)

# Iteration
for key in user:
    print(key)

for key, value in user.items():
    print(f"{key}: {value}")

print(list(user.keys()))         # → ['name', 'age', 'email', 'phone']
print(list(user.values()))       # → ['Budi Santoso', 28, '[email protected]', '...']

set — Collection of Unique Values #

# Creating a set
fruits = {"apple", "orange", "mango", "apple"}   # duplicates removed automatically
print(fruits)   # → {'apple', 'orange', 'mango'} (order not guaranteed)

# An empty set MUST use set(), not {}
empty = set()    # ✓ empty set
not_a_set = {}   # ✗ this is an empty dict, not a set!

# Set operations
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7}

print(a | b)    # union                  → {1,2,3,4,5,6,7}
print(a & b)    # intersection           → {3,4,5}
print(a - b)    # difference             → {1,2}
print(a ^ b)    # symmetric difference   → {1,2,6,7}

# Common use: removing duplicates from a list
data = [1, 2, 2, 3, 3, 3, 4]
unique = list(set(data))
print(unique)   # → [1, 2, 3, 4] (order not guaranteed)

# Membership check — O(1), much faster than a list
print(3 in a)   # → True

Mutable vs Immutable #

This distinction is crucial to understand — many hidden bugs come from misunderstanding which types are mutable and which aren’t.

Immutable (can't be changed):    Mutable (can be changed):
─────────────────────────────     ────────────────────────
int, float, complex               list
str                               dict
bool                              set
tuple                             bytearray
frozenset
bytes
# Immutable: operations produce a NEW object
s = "hello"
s_new = s.upper()    # s doesn't change, s_new is a new object
print(s)             # → hello
print(s_new)         # → HELLO

# Mutable: operations modify THE SAME object
lst = [1, 2, 3]
lst.append(4)        # lst is modified in place
print(lst)           # → [1, 2, 3, 4]

# Important consequence: mutable objects can't be dict keys
d = {}
d[(1, 2)] = "tuple as key"   # ✓ tuples are immutable
d[[1, 2]] = "list as key"    # ✗ TypeError: unhashable type: 'list'

Type Conversion #

Python provides built-in functions for explicit conversion between types. It’s important to remember: not every conversion will succeed — some can raise exceptions.

# int() — convert to integer
print(int("42"))        # → 42
print(int(3.99))        # → 3  (truncated, not rounded!)
print(int(True))        # → 1
print(int("0xFF", 16))  # → 255 (hex string to int)

# These will fail:
# int("3.14")    → ValueError: invalid literal for int()
# int("hello")   → ValueError

# float() — convert to float
print(float("3.14"))    # → 3.14
print(float(42))        # → 42.0
print(float("inf"))     # → inf

# str() — convert to string (always succeeds)
print(str(42))          # → "42"
print(str(3.14))        # → "3.14"
print(str(True))        # → "True"
print(str(None))        # → "None"
print(str([1, 2, 3]))   # → "[1, 2, 3]"

# bool() — convert to boolean
print(bool(0))          # → False
print(bool(""))         # → False
print(bool([]))         # → False
print(bool(42))         # → True
print(bool("false"))    # → True  (any non-empty string is True!)

# Collection conversion
print(list((1, 2, 3)))       # tuple → list: [1, 2, 3]
print(tuple([1, 2, 3]))      # list → tuple: (1, 2, 3)
print(set([1, 2, 2, 3]))     # list → set:   {1, 2, 3}
print(list("hello"))         # str → list:   ['h','e','l','l','o']
print("".join(['h','i']))    # list → str:   "hi"

Safe Conversion with Error Handling #

def to_int_safe(value, default=0):
    """Convert to int without raising an exception."""
    try:
        return int(value)
    except (ValueError, TypeError):
        return default

print(to_int_safe("42"))       # → 42
print(to_int_safe("abc"))      # → 0 (default)
print(to_int_safe("abc", -1))  # → -1 (custom default)
print(to_int_safe(None))       # → 0

Checking Data Types #

x = 42

# type() — returns the exact type
print(type(x))              # → <class 'int'>
print(type(x) == int)       # → True

# isinstance() — checks the type including subclasses (more recommended)
print(isinstance(x, int))           # → True
print(isinstance(x, (int, float)))  # → True (checks several types at once)
print(isinstance(True, int))        # → True (bool is a subclass of int)

# ANTI-PATTERN: type() for type checks — doesn't recognize subclasses
print(type(True) == int)    # → False  (even though True is a subclass of int)

# CORRECT: isinstance() for type checks
print(isinstance(True, int))  # → True

Summary #

  • int is unbounded — no need to worry about overflow like in C/Java. Python manages memory for large integers automatically.
  • Don’t compare floats with == — use math.isclose(). The binary representation of floats isn’t exact for most decimals.
  • Don’t use float for money calculations — use decimal.Decimal for correct financial precision.
  • Strings are immutable — every string operation produces a new object. Use "".join(list) instead of concatenation in loops.
  • None is always compared with is/is not, not ==/!=.
  • An empty set must be set(), not {} — empty curly braces are an empty dict.
  • Mutable objects can’t be dict keys — use tuples (immutable) as keys, not lists.
  • isinstance() is better than type() — it recognizes subclasses and is more flexible for type checking.
  • bool("false") evaluates to True — any non-empty string is truthy, including the strings "false", "0", and "None".

← Previous: Constants   Next: Operators →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact