Skip to main content

Command Palette

Search for a command to run...

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.

Updated
20 min read

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 valuesTrue 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. 🐍