Python Syntax and Data Structures

Variables, indentation-based blocks, core types, lists, tuples, dicts, sets, comprehensions and slicing.

Variables and dynamic typing

Python has no type declarations — a name is bound to a value with =, and its type is whatever that value's type is. The same name can even be rebound to a value of a completely different type later (though doing so on purpose is usually a code smell):

Python
age = 25          # age is bound to an int
age = "twenty-five"  # now age is bound to a str — perfectly legal

This is called dynamic typing: types are checked at runtime, attached to values, not to the variable names that reference them. Contrast this with a statically typed language like Java or C#, where int age = 25; fixes age's type forever at compile time.

Variable names follow snake_case by convention (total_price, user_name), not camelCase.

Indentation defines blocks

Python has no { } braces and no begin/end keywords — a colon followed by consistent indentation is what defines a block:

Python
age = 20

if age >= 18:
    print("Adult")
    print("Can vote")
else:
    print("Minor")

The standard convention (PEP 8) is 4 spaces per indentation level. Mixing tabs and spaces in the same file is a TabError in Python 3 — pick one and let your editor enforce it.

Core built-in types

Python
count = 10          # int — arbitrary precision, no overflow
price = 19.99        # float — 64-bit double precision
name = "Ada"          # str — immutable sequence of Unicode characters
is_active = True     # bool — True / False (capitalized!)
result = None        # NoneType — Python's "no value", like null

print(type(count))   # <class 'int'>

None is Python's null equivalent — used for "no value" defaults, missing return values, and absence in general. Comparing to it should use is None, not == None (see the Common mistakes section below).

Lists — mutable, ordered

A list is Python's general-purpose, mutable, ordered collection:

Python
fruits = ["apple", "banana", "cherry"]

fruits.append("date")        # ["apple", "banana", "cherry", "date"]
fruits[0] = "avocado"         # replace by index
fruits.remove("banana")      # remove by value

print(len(fruits))            # 4
print("cherry" in fruits)     # True

Tuples — immutable, ordered

A tuple looks like a list but cannot be modified after creation. Use tuples for fixed collections — coordinates, RGB values, a function returning multiple values:

Python
point = (3, 4)
x, y = point          # unpacking

# point[0] = 5        # TypeError: 'tuple' object does not support item assignment

Dictionaries — key/value pairs

A dict maps unique keys to values, and (since Python 3.7) preserves insertion order:

Python
user = {"name": "Ada", "age": 30, "active": True}

print(user["name"])          # Ada
user["age"] = 31             # update
user["email"] = "ada@example.com"  # add a new key

for key, value in user.items():
    print(key, "->", value)

Sets — unique, unordered

A set stores unique values with no duplicates and no guaranteed order, and supports fast membership tests and mathematical set operations:

Python
tags = {"python", "web", "python"}   # duplicate is silently dropped
print(tags)                           # {'python', 'web'}

a = {1, 2, 3}
b = {2, 3, 4}
print(a & b)   # intersection: {2, 3}
print(a | b)   # union: {1, 2, 3, 4}
print(a - b)   # difference: {1}

Comparing the four collection types

Type Ordered Mutable Duplicates allowed Typical use
list Yes Yes Yes A general-purpose sequence you'll modify
tuple Yes No Yes Fixed-size, fixed data (coordinates, records)
dict Yes (insertion order) Yes Keys must be unique Key → value lookups
set No Yes No Uniqueness, fast membership checks

Comprehensions

A list comprehension builds a new list from an iterable in a single, readable expression — idiomatic Python prefers this over a manual for loop with .append():

Python
numbers = [1, 2, 3, 4, 5]

squares = [n ** 2 for n in numbers]              # [1, 4, 9, 16, 25]
evens = [n for n in numbers if n % 2 == 0]       # [2, 4]

Dict comprehensions work the same way:

Python
words = ["apple", "kiwi", "banana"]
lengths = {word: len(word) for word in words}
# {'apple': 5, 'kiwi': 4, 'banana': 6}

Slicing

Any sequence (str, list, tuple) supports sequence[start:stop:step]start is inclusive, stop is exclusive, and either can be omitted:

Python
letters = "abcdefgh"

print(letters[2:5])     # 'cde'  (index 2 up to, not including, 5)
print(letters[:3])      # 'abc'  (from the start)
print(letters[5:])      # 'fgh'  (to the end)
print(letters[::2])     # 'aceg' (every second character)
print(letters[::-1])    # 'hgfedcba' (reversed)

Common mistakes

  • Comparing to None with == None instead of is None — it works in practice because None only ever equals itself, but is None is the idiomatic, explicitly-correct form and avoids surprises if __eq__ is ever overridden.
  • Assuming a list assignment copies the list: b = a makes b point at the same list object as a — mutating one mutates the other. Use b = a.copy() or b = list(a) for an independent copy.
  • Trying to mutate a tuple, or use a mutable type (like a list) as a dictionary key or set element — both require hashable, immutable keys.

Interview questions

Q: What's the practical difference between a list and a tuple? Both are ordered sequences, but a list is mutable (you can append, remove, or reassign elements) while a tuple is immutable once created. Tuples are also slightly more memory-efficient and can be used as dictionary keys, since they're hashable — a list cannot be.

Q: Why does Python use indentation instead of braces? It was a deliberate design choice by Guido van Rossum to force consistent, readable formatting — since indentation is the block structure, there's no way to have code that looks nested but isn't (a class of bugs braces-based languages are prone to when indentation and actual scope disagree).