Variables & Data Types
Storing and working with different kinds of data
Variables are containers for storing data values. Unlike many other languages, Python has no command for declaring a variable — a variable is created the moment you first assign a value to it.
Python has several built-in data types. Understanding when to use each type is fundamental to writing effective Python code.
Numeric Data Types
Python has three numeric types:
- int — whole numbers with no size limit:
42,-7,1_000_000 - float — 64-bit decimal numbers:
3.14,2.5e3(= 2500.0) - complex — real + imaginary:
3 + 4j
Key built-in functions:
abs(x)— absolute valueround(x, n)— round to n decimal placespow(x, y)— same asx ** ydivmod(x, y)— returns(quotient, remainder)as a tupleint(x),float(x),str(x)— type conversion
Note: int(3.9) gives 3 — it truncates, it does not round.
# ── int ──────────────────────────────────────────────────────x = 42big = 1_000_000 # underscores improve readabilitybin_ = 0b1010 # binary literal → 10hex_ = 0xFF # hex literal → 255print(x, big, bin_, hex_) # ── float ─────────────────────────────────────────────────────pi = 3.14159sci = 2.5e3 # scientific notation → 2500.0neg = -1.5e-2 # → -0.015print(pi, sci, neg) # ── complex ───────────────────────────────────────────────────c = 3 + 4jprint(c.real, c.imag) # 3.0 4.0print(abs(c)) # 5.0 (magnitude √(3²+4²)) # ── type conversion ───────────────────────────────────────────print(int(3.9)) # 3 ← truncates, does NOT roundprint(float(7)) # 7.0print(str(42)) # '42'print(bool(0)) # False (0 is falsy)print(bool(-5)) # True (any non-zero is truthy) # ── useful built-ins ──────────────────────────────────────────print(abs(-15)) # 15print(round(3.14159, 2)) # 3.14print(pow(2, 10)) # 1024print(divmod(17, 5)) # (3, 2) quotient and remainderprint(max(3, 7, 1, 9)) # 9print(min(3, 7, 1, 9)) # 1
String Data Type
Strings are immutable sequences of characters. All string methods return a new string — they never modify the original.
Use single '...' or double "..." quotes. Triple quotes """...""" span multiple lines.
Case & whitespace: upper(), lower(), title(), strip(), lstrip(), rstrip()
Search: find(s) → index or -1, count(s), startswith(s), endswith(s)
Test: isdigit(), isalpha(), isalnum(), isspace()
Modify: replace(old, new), split(sep), join(iterable)
Format: f-strings f"..." — embed any expression inside {}
s = " Hello, Python World! " # ── case & whitespace ─────────────────────────────────────────print(s.strip()) # "Hello, Python World!"print(s.lower()) # " hello, python world! "print(s.upper()) # " HELLO, PYTHON WORLD! "print(s.title()) # " Hello, Python World! "print("hElLo".swapcase()) # "HeLlO" # ── search ────────────────────────────────────────────────────t = "Hello, Python World!"print(t.find("Python")) # 7 (index; -1 if not found)print(t.count("l")) # 3print(t.startswith("Hello")) # Trueprint(t.endswith("!")) # True # ── test ──────────────────────────────────────────────────────print("123".isdigit()) # Trueprint("abc".isalpha()) # Trueprint("abc123".isalnum()) # True # ── modify ────────────────────────────────────────────────────print(t.replace("Python", "Data Science"))print("a,b,c,d".split(",")) # ['a', 'b', 'c', 'd']print("-".join(["2024", "01", "15"])) # '2024-01-15' # ── f-strings ─────────────────────────────────────────────────name = "Alice"score = 98.567print(f"{name} scored {score:.2f}%") # Alice scored 98.57%print(f"{'Python':^20}") # centred in 20 charsprint(f"{42:08b}") # 00101010 (binary)
Indexing & Slicing
Python uses zero-based indexing — the first element is at index 0. Negative indices count from the right (-1 is the last element).
Slice syntax: seq[start : stop : step]
start— inclusive, defaults to0stop— exclusive, defaults to endstep— skip size, defaults to1; use-1to reverse
Slicing works identically on strings, lists, and tuples. It always returns a new object — the original is unchanged.
# ── indexing ──────────────────────────────────────────────────text = "Python"nums = [10, 20, 30, 40, 50] print(text[0]) # P first characterprint(text[-1]) # n last characterprint(nums[2]) # 30print(nums[-2]) # 40 # ── slicing strings ───────────────────────────────────────────print(text[0:3]) # Pyt indices 0, 1, 2print(text[2:]) # thon from index 2 to endprint(text[:3]) # Pyt from start to index 2print(text[::2]) # Pto every 2nd characterprint(text[::-1]) # nohtyP reversed # ── slicing lists ─────────────────────────────────────────────print(nums[1:4]) # [20, 30, 40]print(nums[::2]) # [10, 30, 50]print(nums[::-1]) # [50, 40, 30, 20, 10] # ── slicing returns a NEW object ─────────────────────────────subset = nums[1:3]print(subset) # [20, 30]print(nums) # [10, 20, 30, 40, 50] unchanged
Lists
A list is an ordered, mutable sequence. You can add, remove, and change elements after creation. Lists can hold mixed types.
Add: append(x), insert(i, x), extend(iterable)
Remove: remove(x) by value, pop(i) by index (returns the item), clear()
Search: index(x) → first position, count(x) → occurrences
Order: sort() modifies in place; sorted(lst) returns a new sorted list
Copy: copy() — shallow copy; without it, two variables share the same list.
fruits = ["apple", "banana", "cherry"] # ── add ───────────────────────────────────────────────────────fruits.append("date") # add to endfruits.insert(1, "avocado") # insert at index 1fruits.extend(["elderberry", "fig"]) # add multipleprint(fruits) # ── remove ────────────────────────────────────────────────────fruits.remove("avocado") # remove by value (first match)popped = fruits.pop() # remove & return last itemprint(f"Popped: {popped}")print(fruits) # ── search ────────────────────────────────────────────────────nums = [3, 1, 4, 1, 5, 9, 2, 6]print(nums.index(4)) # 2 index of first 4print(nums.count(1)) # 2 how many 1s # ── sort ──────────────────────────────────────────────────────nums.sort() # sort in placeprint(nums)words = ["banana", "apple", "cherry"]print(sorted(words)) # new sorted list ← original unchangedprint(words) # unchanged # ── mutability: two variables, same list ─────────────────────a = [1, 2, 3]b = a # b points to the SAME list!b.append(4)print(a) # [1, 2, 3, 4] ← also changedc = a.copy() # c is a separate copyc.append(99)print(a) # [1, 2, 3, 4] ← unchanged
Tuples
A tuple is an ordered, immutable sequence. Once created, you cannot add, remove, or change its elements.
Use tuples for data that should not change — coordinates, RGB colours, function return values. A single-element tuple needs a trailing comma: (42,).
Advantages over lists:
- Faster iteration and access
- Can be used as dictionary keys (lists cannot)
- Signal to other developers: "this data must not change"
Tuple unpacking lets you assign multiple variables in one line. Use * to capture the remaining elements.
# ── creating tuples ───────────────────────────────────────────point = (3, 7)rgb = (255, 128, 0)single = (42,) # trailing comma required for single item! # ── access (same as list, but read-only) ─────────────────────print(point[0]) # 3print(rgb[-1]) # 0print(rgb[0:2]) # (255, 128) # ── tuple unpacking ───────────────────────────────────────────x, y = pointprint(f"x={x}, y={y}") # x=3, y=7 r, g, b = rgbprint(f"R={r} G={g} B={b}") first, *rest = (1, 2, 3, 4, 5)print(first) # 1print(rest) # [2, 3, 4, 5] # ── immutable: cannot change elements ─────────────────────────try: point[0] = 10except TypeError as e: print(f"TypeError: {e}") # ── tuple as dict key (lists cannot be dict keys) ────────────locations = {(28.6, 77.2): "Delhi", (19.1, 72.9): "Mumbai"}print(locations[(28.6, 77.2)]) # Delhi # ── count & index methods ─────────────────────────────────────t = (1, 2, 2, 3, 2)print(t.count(2)) # 3print(t.index(3)) # 3
Dictionaries
A dictionary stores key-value pairs. Keys must be unique and immutable. Values can be any type. Dicts are mutable and, since Python 3.7, preserve insertion order.
Access: d[key] raises KeyError if missing; d.get(key, default) returns None (or a default) instead — prefer get() for safety.
Views (live, not copies): keys(), values(), items()
Modify: update(), pop(key), setdefault(key, val) (adds only if key absent)
person = { "name": "Alice", "age": 28, "city": "Jaipur", "skills": ["Python", "SQL"]} # ── access ────────────────────────────────────────────────────print(person["name"]) # Aliceprint(person.get("salary")) # None (no KeyError)print(person.get("salary", 0)) # 0 (safe default) # ── add / update ──────────────────────────────────────────────person["email"] = "alice@example.com" # add new keyperson["age"] = 29 # update existingperson.update({"city": "Delhi", "role": "analyst"})print(person) # ── remove ────────────────────────────────────────────────────removed = person.pop("email") # removes & returns valueprint(f"Removed: {removed}") # ── iterate with items() ──────────────────────────────────────for key, value in person.items(): print(f" {key}: {value}") # ── check membership ──────────────────────────────────────────print("name" in person) # Trueprint("salary" in person) # False # ── setdefault: insert only if key is absent ─────────────────person.setdefault("country", "India")print(person["country"]) # India
for Loops & range()
The for loop iterates over any iterable — lists, tuples, strings, dicts, and range objects.
range() variants:
range(n)→ 0 to n−1range(start, stop)→ start to stop−1 (stop is exclusive)range(start, stop, step)→ with step size (use negative step to count down)
Useful tools:
enumerate(iterable)— yields(index, value)pairszip(a, b)— pairs elements of two iterables side by side
# ── range variants ────────────────────────────────────────────for i in range(5): print(i, end=" ") # 0 1 2 3 4print() for i in range(1, 6): print(i, end=" ") # 1 2 3 4 5print() for i in range(0, 20, 5): print(i, end=" ") # 0 5 10 15print() for i in range(10, 0, -2): print(i, end=" ") # 10 8 6 4 2print() # ── iterating a list and a string ─────────────────────────────fruits = ["apple", "banana", "cherry"]for fruit in fruits: print(fruit.upper()) for char in "Python": print(char, end="-") # P-y-t-h-o-n-print() # ── enumerate: index + value ──────────────────────────────────for i, fruit in enumerate(fruits, start=1): print(f"{i}. {fruit}") # ── zip: pair two lists ───────────────────────────────────────names = ["Alice", "Bob", "Charlie"]scores = [92, 85, 78]for name, score in zip(names, scores): print(f"{name}: {score}") # ── iterating a dictionary ────────────────────────────────────grades = {"Maths": "A", "Science": "B", "English": "A"}for subject, grade in grades.items(): print(f"{subject} → {grade}")
while Loops
A while loop runs as long as its condition is True. Use it when you don't know the number of iterations in advance.
Always ensure the loop will eventually terminate — an infinite loop freezes your program. Use a counter, a flag variable, or a break statement as an exit strategy.
while-else: the else block runs only when the condition becomes False naturally — it is skipped if the loop exits via break.
# ── basic while ───────────────────────────────────────────────count = 1while count <= 5: print(f"Count: {count}") count += 1print("Done!") # ── countdown ─────────────────────────────────────────────────n = 10while n > 0: print(n, end=" ") n -= 3print(f"\nFinal n: {n}") # will be negative # ── while-else ────────────────────────────────────────────────attempts = 0while attempts < 3: attempts += 1 print(f"Attempt {attempts}")else: print("All attempts used — else block ran") # runs naturally # ── search with while (break skips else) ──────────────────────items = [4, 7, 2, 9, 1, 5]target = 9i = 0while i < len(items): if items[i] == target: print(f"Found {target} at index {i}") break i += 1else: print(f"{target} not found") # skipped because break fired
break, continue & pass
Three statements give fine-grained control inside any loop:
break— exit the loop immediately; also skips theelseclausecontinue— skip the rest of the current iteration and jump to the next onepass— do nothing; a placeholder so Python doesn't raise a syntax error on an empty block. Useful while drafting code.
All three work in both for and while loops.
# ── break: exit loop early ────────────────────────────────────print("-- break --")for i in range(1, 10): if i == 5: print("Reached 5, stopping!") break print(i) # prints 1 2 3 4 # ── continue: skip current iteration ─────────────────────────print("-- continue --")for i in range(1, 8): if i % 2 == 0: continue # skip even numbers print(i) # prints 1 3 5 7 # ── pass: empty placeholder ───────────────────────────────────print("-- pass --")for i in range(5): if i == 2: pass # TODO: handle this case later else: print(i) # prints 0 1 3 4 # ── practical: break + else pattern ──────────────────────────numbers = [3, 7, 12, 5, 8]for num in numbers: if num > 10: print(f"First number > 10: {num}") breakelse: print("No number greater than 10") # runs only if no break
List Comprehension
List comprehension is a concise, readable way to create lists from iterables — replacing a loop + append with a single expression.
Syntax: [expression for item in iterable if condition]
The if condition part is optional — it filters which items are included. List comprehensions are also faster than equivalent for-loop + append code.
# ── basic: transform each element ─────────────────────────────squares = [x**2 for x in range(1, 6)]print(squares) # [1, 4, 9, 16, 25] # ── with condition: filter ────────────────────────────────────evens = [x for x in range(20) if x % 2 == 0]print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] # ── transform strings ─────────────────────────────────────────words = ["hello", "world", "python"]upper = [w.upper() for w in words]print(upper) # ['HELLO', 'WORLD', 'PYTHON'] # ── filter AND transform together ─────────────────────────────fruits = ["apple", "Banana", "cherry", "Date", "elderberry"]long_lower = [f.lower() for f in fruits if len(f) > 5]print(long_lower) # ['banana', 'cherry', 'elderberry'] # ── equivalent for-loop (more verbose) ────────────────────────result = []for f in fruits: if len(f) > 5: result.append(f.lower())print(result) # same result # ── nested: flatten a 2D list ─────────────────────────────────matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]flat = [num for row in matrix for num in row]print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Mutability
Mutability determines whether an object's value can be changed after it is created.
Immutable — cannot be modified in place:
int,float,complex,boolstr— string methods always return a new stringtuple— elements cannot be reassigned or added
Mutable — can be modified in place:
list— append, remove, sort all change the originaldict— add, update, delete keys in placeset— add/remove elements in place
Why it matters: assigning a mutable object to two variables makes both point to the same object. Use .copy() (or copy.deepcopy() for nested structures) to get an independent copy.
# ── immutable int: reassignment creates a NEW object ─────────x = 10y = xx = 20print(y) # 10 — y is unaffected # ── immutable str: methods return a new string ───────────────s = "hello"t = ss = s.upper() # s now points to "HELLO"print(t) # "hello" — t still points to original # ── mutable list: both variables share the same object ───────a = [1, 2, 3]b = a # b is NOT a copy!b.append(4)print(a) # [1, 2, 3, 4] ← a also changed # ── fix: use .copy() ──────────────────────────────────────────c = a.copy()c.append(99)print(a) # [1, 2, 3, 4] ← unchangedprint(c) # [1, 2, 3, 4, 99] # ── mutable dict: same shared-reference behaviour ────────────d1 = {"x": 1}d2 = d1d2["y"] = 2print(d1) # {'x': 1, 'y': 2} ← shared! # ── immutable tuple: cannot change elements ──────────────────try: (1, 2, 3)[0] = 99except TypeError as e: print(f"TypeError: {e}") # ── quick reference ───────────────────────────────────────────print("Mutable :", ["list", "dict", "set"])print("Immutable:", ["int", "float", "str", "tuple", "bool"])
Practice Questions
Question 1
What is the result of 10 // 3 in Python?
Question 2
What does "Python"[-1] return?
Question 3
What is the output of "abcdef"[1:4]?
Question 4
What does [1, 2, 3].append(4) return?
Question 5
Which of the following will raise a TypeError?
Question 6
How do you safely access a dictionary key that might not exist (without raising KeyError)?
Question 7
What does list(range(2, 10, 3)) produce?
Question 8
What happens when break is encountered inside a for loop?
Question 9
What is printed by: for i in range(5):\n if i == 2: continue\n print(i, end=' ')
Question 10
What is the purpose of 'pass' in Python?
Question 11
What does [x**2 for x in range(1, 6)] produce?
Question 12
Which of the following is immutable in Python?