Python's Core Data Types: Numbers, Strings & Booleans — From First Principles
A fresher's deep-dive into the three types you'll use every single day.
Every program you will ever write — no matter how complex — ultimately manipulates three kinds of things: numbers, text, and yes/no answers. Python calls these int/float/complex, str, and bool.
This blog unpacks all three from the ground up — what they are, how they work, and why they work that way.
🔢 Part 1: Numeric Types
What Is an Integer (int)?
An integer is a whole number — no decimal point, no fractional part. Positive, negative, or zero.
students = 42
temperature = -7
balance = 0
In many languages, integers have a fixed size — typically 32 or 64 bits, which limits how large they can be. Python's int has no such limit (more on this shortly — it's one of Python's most surprising features).
Analogy: An integer is like a headcount. You can have 42 students, not 42.7 students. It's always a clean, exact whole number. No fractions allowed.
What Is a Floating-Point Number (float)?
A float is a number with a decimal point — it can represent fractions and very large or very small values.
pi = 3.14159
temperature = -7.5
price = 0.99
scientific = 1.5e10 # 1.5 × 10^10 = 15,000,000,000
tiny = 2.3e-4 # 0.00023
The name "floating-point" comes from how these numbers are stored internally: the decimal point can float to different positions to represent a wide range of magnitudes. The underlying format is called IEEE 754 double precision — 64 bits divided into a sign, exponent, and fraction.
Analogy: A float is like a measuring tape — it can express 1.75 metres, 0.003 millimetres, or 150,000 kilometres. It trades absolute precision for range.
What Is a Complex Number (complex)?
A complex number has two parts: a real part and an imaginary part, written as a + bj in Python (mathematicians write a + bi, but Python uses j).
z1 = 3 + 4j
z2 = complex(2, -1) # 2 - 1j
print(z1.real) # 3.0
print(z1.imag) # 4.0
You won't need complex numbers for most beginner work. They're essential in signal processing, electrical engineering, and advanced mathematics. Python includes them as a first-class type so scientists and engineers don't need an external library for something this fundamental.
How Are Arithmetic Operators Implemented?
When you write 2 + 3, Python isn't doing magic — it's calling a method on the integer object.
2 + 3 # readable syntax
(2).__add__(3) # exactly the same thing, under the hood
Every arithmetic operator maps to a special method called a dunder method (double underscore). Python is just giving you clean syntax as a shortcut.
| Operator | Dunder Method | Example |
|---|---|---|
+ |
__add__ |
5 + 3 → 8 |
- |
__sub__ |
5 - 3 → 2 |
* |
__mul__ |
5 * 3 → 15 |
/ |
__truediv__ |
5 / 3 → 1.666... |
// |
__floordiv__ |
5 // 3 → 1 |
% |
__mod__ |
5 % 3 → 2 |
** |
__pow__ |
5 ** 3 → 125 |
This matters because it means you can define your own objects that support +, -, * etc. by implementing these methods — Python's operator system is completely extensible.
The Difference Between / and //
This is one of Python's most important distinctions for beginners.
/ — True Division: Always returns a float, even if the result is a whole number.
10 / 2 # 5.0 ← float, not 5
7 / 2 # 3.5
// — Floor Division: Divides and then rounds down to the nearest whole number. Returns an int if both operands are ints.
7 // 2 # 3 ← 3.5 rounded DOWN to 3
-7 // 2 # -4 ← -3.5 rounded DOWN to -4 (not -3!)
The "floor" in floor division means "always round toward negative infinity" — not "always round toward zero." This surprises people with negative numbers.
Analogy: You have 7 cookies to share equally among 2 friends.
/gives you the exact answer: 3.5 cookies each.
//gives you the practical answer: 3 whole cookies each (you can't hand someone half a cookie at a party).
The Modulus Operator %
The % operator returns the remainder after floor division.
7 % 3 # 1 → 7 = 3×2 + 1, remainder is 1
10 % 5 # 0 → divides evenly, no remainder
13 % 4 # 1 → 13 = 4×3 + 1
This sounds academic, but it's incredibly useful in practice:
# Is a number even or odd?
number = 42
if number % 2 == 0:
print("even") # any even number leaves remainder 0 when divided by 2
# Wrap around a circular list (like clock hours)
hour = (current_hour + 5) % 24 # works even past midnight
Analogy: Think of a clock. After 12 comes 1, not 13. That wrapping behaviour is exactly
% 12. The modulus operator is the mathematical engine behind anything that cycles or wraps around.
The Exponentiation Operator **
** raises a number to a power. It's Python's equivalent of the "x^y" button on a calculator.
2 ** 10 # 1024 → 2 to the power of 10
3 ** 3 # 27 → cube of 3
16 ** 0.5 # 4.0 → square root (power of 0.5)
2 ** -1 # 0.5 → reciprocal
Analogy: Multiplication is repeated addition (
3 * 4 = 3+3+3+3). Exponentiation is repeated multiplication (3 ** 4 = 3×3×3×3). Each operator is just the next level up in the tower of repeated operations.
Numeric Overflow in Python
In most languages, integers have a fixed maximum size. In C, an int maxes out at 2,147,483,647 (32-bit). Go beyond that, and the number silently wraps around to a negative number — a catastrophic, invisible bug.
Python sidesteps this entirely: Python integers never overflow.
# This works perfectly in Python
print(2 ** 1000)
# 10715086071862673209484250490600018105614048117055336074437503883703510511249
# 36122493198378815695858127594672917553146825187145285692314043598457757469857
# 480393456777482423098542107460506237114187795418215304647498358194126739876755
# ... (a genuinely enormous number)
Python allocates as much memory as needed to store the number, no matter how large. This is called arbitrary precision integers.
Analogy: Most languages give you a fixed-size jar to store numbers. If your number doesn't fit, it overflows — and you don't even get a warning, the jar just wraps around. Python gives you a jar that grows to fit whatever you put in it. There's a memory cost, but you never silently get the wrong answer.
This makes Python ideal for cryptography, number theory, and anywhere exact large-integer arithmetic matters.
Floating-Point Precision Limitations
Floats do NOT have arbitrary precision. They trade precision for range, and this creates a famous gotcha:
0.1 + 0.2 # 0.30000000000000004 ← not 0.3!
0.1 + 0.2 == 0.3 # False
Why? Because 0.1 cannot be represented exactly in binary, just like 1/3 cannot be written as a finite decimal. The number stored is the closest possible approximation in 64 bits.
This is a property of IEEE 754 floating-point arithmetic — shared by virtually every programming language, not just Python.
# The right way to compare floats:
import math
math.isclose(0.1 + 0.2, 0.3) # True ✅
# For financial calculations, use the Decimal module:
from decimal import Decimal
Decimal("0.1") + Decimal("0.2") # Decimal('0.3') ✅
Analogy: Try writing 1/3 as a decimal. You get 0.333333... — it goes on forever. At some point you have to stop writing and accept a tiny error. Floating-point numbers do exactly this, but in binary, and at the 15th-16th decimal digit of precision. For science, that's fine. For money, use
Decimal.
📝 Part 2: Strings
What Is a String?
A string is a sequence of characters — letters, digits, symbols, spaces, emoji, anything textual.
name = "Riya"
city = "Pune"
message = "Hello, World!"
emoji_test = "Python is 🔥"
Internally, each character is stored as a number (its Unicode code point). The string "ABC" is stored as [65, 66, 67] — Python just shows it to you as text.
Analogy: A string is like a train. Each carriage (character) has a position — a numbered seat. The first carriage is seat 0, the next is seat 1, and so on. You can look at any single carriage, take a range of carriages, or count how many carriages the train has.
String Creation Methods
# Single quotes
name = 'Riya'
# Double quotes
name = "Riya"
# Triple single quotes
message = '''This spans
multiple lines'''
# Triple double quotes
message = """This also spans
multiple lines"""
# String constructor
name = str(42) # "42" — converts other types to string
All of these create str objects. The quotes are just syntax — they don't affect the value.
Single Quotes vs Double Quotes
Python treats them identically. The choice is mostly about convenience when your string contains quotes:
# Without thinking:
message = 'She said "hello"' # double quotes inside single — no escaping needed
message = "It's a good day" # apostrophe inside double — no escaping needed
# The escape route (works but cluttered):
message = 'It\'s a good day' # backslash escapes the apostrophe
Pick one style and stay consistent within a project. Most Python style guides default to double quotes.
Triple-Quoted Strings
Triple quotes let you write strings that span multiple lines naturally, without special characters:
poem = """
Roses are red,
Violets are blue,
Python is awesome,
And so are you.
"""
sql_query = """
SELECT name, age
FROM users
WHERE active = 1
"""
They're also used for docstrings — the documentation strings that explain what a function does:
def add(a, b):
"""
Adds two numbers and returns the result.
Works with both ints and floats.
"""
return a + b
String Indexing
Because a string is a sequence, each character has a position number called an index. Python uses zero-based indexing — the first character is at position 0.
name = "Python"
# P y t h o n
# index: 0 1 2 3 4 5
# neg: -6 -5 -4 -3 -2 -1
print(name[0]) # 'P'
print(name[3]) # 'h'
print(name[-1]) # 'n' ← negative index counts from the end
print(name[-2]) # 'o'
Negative indices are genuinely useful — name[-1] gives you the last character without needing to know the length.
Analogy: The train again. Seat 0 is the first carriage. Seat -1 is the last carriage, seat -2 is second-to-last. Negative indices let you count from the back without knowing exactly how long the train is.
String Slicing
Slicing extracts a substring — a portion of the string.
text = "Hello, World!"
# 0123456789...
text[0:5] # "Hello" → from index 0 up to (not including) 5
text[7:] # "World!" → from index 7 to the end
text[:5] # "Hello" → from start up to (not including) 5
text[::2] # "Hlo ol!" → every 2nd character (step of 2)
text[::-1] # "!dlroW ,olleH" → reversed string (step of -1)
The full slicing syntax is [start:stop:step]. Any part can be omitted and Python fills in sensible defaults.
Analogy: Slicing is like photocopying a specific range of pages from a book.
[7:12]says "give me pages 7 through 11." The original book is untouched — you just get a copy of those pages.
String Concatenation and Repetition
Concatenation joins strings together using +:
first = "Hello"
second = "World"
combined = first + ", " + second + "!"
print(combined) # "Hello, World!"
Repetition duplicates a string using *:
print("Ha" * 3) # "HaHaHa"
print("-" * 40) # "----------------------------------------"
print("⭐" * 5) # "⭐⭐⭐⭐⭐"
Both operators are the same symbols as arithmetic — Python knows to treat them differently because the values are strings, not numbers. (Remember: types determine what operators do.)
String Immutability
This is one of the most important properties of Python strings: once created, a string cannot be changed.
name = "Python"
name[0] = "J" # ❌ TypeError: 'str' object does not support item assignment
You can't reach in and swap a character. You can only create a new string:
name = "Python"
new_name = "J" + name[1:] # "Jython" — a brand new string object
Why is immutability good? It makes strings safe to share. If you pass a string to a function, you know the function can't secretly modify it. It also allows Python to optimise memory — identical strings can share the same memory location safely, because neither can change.
Analogy: A printed book is immutable. You can't change a word on page 47 — you'd need to reprint the book. But you can write your own new book that quotes or modifies parts of the old one. The original remains unchanged.
Common String Methods
Python strings come with a rich built-in toolkit:
text = " Hello, World! "
# Case
text.upper() # " HELLO, WORLD! "
text.lower() # " hello, world! "
text.title() # " Hello, World! "
# Whitespace
text.strip() # "Hello, World!" removes leading/trailing spaces
text.lstrip() # "Hello, World! " removes only left spaces
text.rstrip() # " Hello, World!" removes only right spaces
# Search
"World" in text # True
text.find("World") # 8 → index where "World" starts (-1 if not found)
text.count("l") # 3 → how many times "l" appears
# Replace & split
text.replace("World", "Python") # " Hello, Python! "
"a,b,c".split(",") # ["a", "b", "c"]
",".join(["a", "b", "c"]) # "a,b,c"
# Validation
"hello".isalpha() # True → only letters
"123".isdigit() # True → only digits
" ".isspace() # True → only whitespace
"hello".startswith("he") # True
"hello".endswith("lo") # True
None of these modify the original string — they all return a new string. Remember: strings are immutable.
Unicode Support
Python 3 strings are Unicode by default. This means you can write in virtually any human language, and include emoji, mathematical symbols, and more — no special setup required.
greeting_hindi = "नमस्ते"
greeting_arabic = "مرحبا"
greeting_emoji = "Hello 🌍"
math_symbols = "π ≈ 3.14159"
print(len("Python")) # 6
print(len("🔥")) # 1 ← one emoji = one character in Python 3
Analogy: ASCII (the old standard) was like a dictionary that only had English words. Unicode is the Library of Congress of character systems — it has a numbered entry for every character in every human script ever written, plus emoji, technical symbols, and more. Python 3 speaks Unicode natively.
String Encoding and Decoding
When text moves between the human-readable world and the byte world (files, networks, databases), it must be encoded into bytes and later decoded back.
text = "Hello 🌍"
# Encode string → bytes
encoded = text.encode("utf-8")
print(encoded) # b'Hello \xf0\x9f\x8c\x8d'
# Decode bytes → string
decoded = encoded.decode("utf-8")
print(decoded) # "Hello 🌍"
UTF-8 is the dominant encoding — it's efficient, backward-compatible with ASCII, and can represent all Unicode characters. You'll use it almost exclusively.
Analogy: Encoding is like translating a book into Morse code for transmission over telegraph. Decoding is translating the Morse code back into words. The message is the same — the representation changes for the medium. Different encodings (UTF-8, UTF-16, Latin-1) are different transmission formats, each with trade-offs.
✅ Part 3: Booleans
What Is a Boolean?
A Boolean is the simplest possible data type: it has exactly two values — True or False.
is_logged_in = True
has_permission = False
Named after George Boole, the 19th-century mathematician who invented the algebra of logical operations, booleans are the foundation of every decision, every if statement, every loop condition in programming.
Analogy: A boolean is a light switch. It's either on (
True) or off (False). No dimmer. No middle ground. Every decision your program makes eventually reduces to a sequence of these switches being checked.
True vs False
In Python, True and False are capitalised (unlike true/false in JavaScript or Java). They are actually instances of bool, which is a subclass of int:
print(type(True)) # <class 'bool'>
print(True == 1) # True ← bool is a subclass of int
print(False == 0) # True
print(True + True) # 2 ← you can add booleans (not useful, but possible)
This means True literally is 1 and False literally is 0 under the hood — a historical design choice.
Comparison Operators
Comparisons evaluate a relationship between two values and return a boolean:
5 > 3 # True — greater than
5 < 3 # False — less than
5 >= 5 # True — greater than or equal
5 <= 4 # False — less than or equal
5 == 5 # True — equal to (double = !)
5 != 4 # True — not equal to
The most common mistake: confusing = (assignment — puts a value in a variable) with == (comparison — asks "are these equal?").
x = 5 # assignment: x now holds 5
x == 5 # comparison: True — yes, x is 5
x == 6 # comparison: False — no, x is not 6
Analogy:
=is like writing a label on a box.==is like looking at two boxes and asking "do they contain the same thing?". Very different operations that happen to share a similar symbol.
Logical Operators
Logical operators combine multiple boolean values:
and — True only if both sides are True:
age = 20
has_id = True
can_enter = age >= 18 and has_id # True and True → True
or — True if at least one side is True:
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday # True or False → True
not — Flips the value:
is_raining = False
should_go_outside = not is_raining # not False → True
Boolean Algebra Basics
Boolean algebra is the complete set of rules governing how True and False combine. The three fundamental operations — and, or, not — follow these rules:
AND rules:
True and True → True
True and False → False
False and True → False
False and False → False
OR rules:
True or True → True
True or False → True
False or True → True
False or False → False
NOT rules:
not True → False
not False → True
These seven combinations are the complete building blocks of all logical reasoning in computing. Every if statement, every database query filter, every access control rule is assembled from these primitives.
Truth Tables
A truth table is just a systematic way of listing all possible input combinations and their outputs. Here's the complete truth table for all three operators:
A B A and B A or B not A
------ ------ --------- -------- -----
True True True True False
True False False True False
False True False True True
False False False False True
Analogy: A truth table is like the instruction manual for a combination lock. It tells you exactly what output you get for every possible combination of inputs. No guessing, no ambiguity.
Short-Circuit Evaluation
Python is lazy about evaluating logical expressions — in the best possible way.
For and: if the left side is False, Python immediately returns False and never even looks at the right side.
For or: if the left side is True, Python immediately returns True and never evaluates the right side.
# Short-circuit with 'and'
def risky():
print("evaluated!")
return True
False and risky() # "evaluated!" is never printed — risky() is never called
True and risky() # "evaluated!" IS printed
# Short-circuit with 'or'
True or risky() # "evaluated!" is never printed
False or risky() # "evaluated!" IS printed
This isn't just an optimisation — it's actively used as a pattern:
# Safe division — check before dividing
result = denominator != 0 and (numerator / denominator)
# Default value pattern
name = user_input or "Anonymous" # if user_input is empty, use "Anonymous"
Analogy: You're hiring someone and have two requirements: they must pass a background check AND a skills test. If they fail the background check, do you bother scheduling the skills test? No. You short-circuit. Python applies exactly this common-sense efficiency to logical expressions.
Truthiness and Falsiness
Python goes beyond just True and False. Every value has an inherent truthiness, which means any value can be used in a boolean context:
Falsy values (treated as False):
False
None
0 # zero integer
0.0 # zero float
"" # empty string
[] # empty list
{} # empty dict
() # empty tuple
Truthy values (treated as True):
True
42 # any non-zero number
"hello" # any non-empty string
[1, 2, 3] # any non-empty collection
This lets you write very natural-reading conditions:
name = input("Enter your name: ")
if name: # True if name is non-empty
print(f"Hello, {name}!")
else:
print("You didn't enter a name.")
items = []
if not items: # True if items is empty
print("Your cart is empty.")
Analogy: Truthiness is like a "is there anything here?" check. An empty box is falsy — nothing's there. Any box with something in it is truthy. Zero is falsy — nothing there. Any other number means something is there, so it's truthy.
Putting It All Together
Here's how all three type families relate to each other:
ALL VALUES IN PYTHON
│
├── Numeric Types (represent quantities)
│ ├── int → exact whole numbers, arbitrary size, never overflow
│ ├── float → approximate decimals, 64-bit IEEE 754, beware of 0.1+0.2
│ └── complex → real + imaginary, for scientific computing
│
├── str (represents text)
│ ├── Sequence of Unicode characters, zero-indexed
│ ├── Immutable — you create new strings, never modify in place
│ └── Rich method library: strip, split, join, find, replace, encode...
│
└── bool (represents truth values)
├── True / False — the outcome of every decision
├── Built on int: True==1, False==0
├── Combined with and, or, not
└── Every value has truthiness — empty/zero is falsy, everything else truthy
These three type families cover the ground floor of Python. Lists, dictionaries, functions, and classes are all built on top of — or made of — these fundamentals.
When you understand what an int actually is, why 0.1 + 0.2 isn't 0.3, what string immutability means, and how short-circuit evaluation saves work, you're not just learning Python syntax. You're understanding why Python makes the choices it does — and that understanding transfers to every language you'll ever learn.
Next time you write if username and password:, you're doing short-circuit boolean evaluation over two truthy checks. That one line contains all three type families working together. 🐍

