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):
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:
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
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:
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:
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:
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:
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():
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:
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:
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
Nonewith== Noneinstead ofis None— it works in practice becauseNoneonly ever equals itself, butis Noneis the idiomatic, explicitly-correct form and avoids surprises if__eq__is ever overridden. - Assuming a list assignment copies the list:
b = amakesbpoint at the same list object asa— mutating one mutates the other. Useb = a.copy()orb = 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).