Home About lightbulbSkillUp Projects Contact Us
Client Login

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 value
  • round(x, n) — round to n decimal places
  • pow(x, y) — same as x ** y
  • divmod(x, y) — returns (quotient, remainder) as a tuple
  • int(x), float(x), str(x) — type conversion

Note: int(3.9) gives 3 — it truncates, it does not round.

Python
# ── 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 {}

Python
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 to 0
  • stopexclusive, defaults to end
  • step — skip size, defaults to 1; use -1 to reverse

Slicing works identically on strings, lists, and tuples. It always returns a new object — the original is unchanged.

Python
# ── 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.

Python
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.

Python
# ── 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)

Python
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−1
  • range(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) pairs
  • zip(a, b) — pairs elements of two iterables side by side
Python
# ── 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.

Python
# ── 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:

  • breakexit the loop immediately; also skips the else clause
  • continueskip the rest of the current iteration and jump to the next one
  • passdo 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.

Python
# ── 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.

Python
# ── 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, bool
  • str — string methods always return a new string
  • tuple — elements cannot be reassigned or added

Mutable — can be modified in place:

  • list — append, remove, sort all change the original
  • dict — add, update, delete keys in place
  • set — 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.

Python
# ── 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"])
code Python REPL — Try it yourself

        

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?