Python Lists: Everything You Need to Know
Lists are Python's most versatile built-in data structure. They store an ordered collection of values β any mix of types β and they grow or shrink dynamically as you add and remove items. If you only ever learn one data structure in Python, make it the list. It will show up in virtually every program you write.
Creating Lists
Use square brackets [] to create a list literal, or list() to convert another iterable into a list.
# List literal
crops = ["wheat", "carrot", "tomato"]
# Mixed types (valid but uncommon in practice)
mixed = [42, "hello", True, 3.14, None]
# Empty list
inventory = []
# From another iterable
digits = list(range(5)) # [0, 1, 2, 3, 4]
chars = list("farm") # ['f', 'a', 'r', 'm']
print(crops) # ['wheat', 'carrot', 'tomato']
print(len(crops)) # 3
Indexing and Slicing
Lists are zero-indexed β the first item is at index 0. Negative indices count from the end. Slicing extracts a sub-list using [start:stop:step].
values = [10, 25, 15, 40, 30, 5]
print(values[0]) # 10 β first item
print(values[-1]) # 5 β last item
print(values[1:4]) # [25, 15, 40] β index 1 up to (not including) 4
print(values[:3]) # [10, 25, 15] β from start to index 3
print(values[3:]) # [40, 30, 5] β from index 3 to end
print(values[::2]) # [10, 15, 30] β every other item
print(values[::-1]) # [5, 30, 40, 15, 25, 10] β reversed
list[start:stop:step]. Omitting start defaults to 0; omitting stop goes to the end; omitting step defaults to 1.
Common Methods
Lists come packed with methods for adding, removing, searching, and sorting items. Here are the ones you'll use most often:
plots = ["A1", "B2", "C3"]
plots.append("D4") # add to end: ["A1","B2","C3","D4"]
plots.insert(1, "X0") # insert at index 1
plots.remove("B2") # remove first occurrence of "B2"
last = plots.pop() # remove and return last item
plots.sort() # sort in place (alphabetically)
plots.reverse() # reverse in place
idx = plots.index("A1") # find index of first "A1"
count = plots.count("A1") # how many times "A1" appears
plots.clear() # remove all items
# Non-mutating alternatives
crops = ["tomato", "wheat", "carrot"]
sorted_crops = sorted(crops) # returns new sorted list
print(sorted_crops) # ['carrot', 'tomato', 'wheat']
print(crops) # unchanged
len() and the in Operator
len() returns the number of items. The in keyword tests membership.
seeds = ["wheat", "carrot", "pumpkin"]
print(len(seeds)) # 3
print("carrot" in seeds) # True
print("tomato" in seeds) # False
print("tomato" not in seeds) # True
# Guard against adding duplicates
new_crop = "wheat"
if new_crop not in seeds:
seeds.append(new_crop)
else:
print(f"{new_crop} already in inventory")
Iterating Over a List
The most common pattern β use for item in list. When you need the index too, use enumerate().
crops = ["wheat", "carrot", "tomato"]
for crop in crops:
print(f"Watering {crop}")
# With index
for i, crop in enumerate(crops):
print(f"Plot {i}: {crop}")
2D Lists β List of Lists
A list that contains other lists represents a 2D grid β perfect for a farm map. Access cells with two indices: grid[row][col].
# 3x3 grid β None means empty
grid = [
[None, "wheat", None ],
["carrot", None, "tomato"],
[None, "carrot", None ],
]
print(grid[0][1]) # "wheat" β row 0, col 1
print(grid[1][2]) # "tomato" β row 1, col 2
# Count planted plots
planted = sum(1 for row in grid for cell in row if cell is not None)
print(f"Planted: {planted} plots")
List Comprehensions
A list comprehension builds a new list in a single readable expression. The syntax is [expression for item in iterable if condition] β the if part is optional.
nums = [1, -3, 5, -2, 8, 0]
# Keep only positive numbers, doubled
result = [x * 2 for x in nums if x > 0]
print(result) # [2, 10, 16]
# Extract names of mature crops
plots = [
{"name": "wheat", "mature": True},
{"name": "carrot", "mature": False},
{"name": "tomato", "mature": True},
]
ready = [p["name"] for p in plots if p["mature"]]
print(ready) # ['wheat', 'tomato']
# Build a flat list of (row, col) tuples for a 3x3 grid
coords = [(r, c) for r in range(3) for c in range(3)]
print(coords[:4]) # [(0,0),(0,1),(0,2),(1,0)]
Copying Lists
Simply writing b = a doesn't copy a list β both variables point to the same object. Use slicing, .copy(), or list() for a shallow copy. For nested structures (list of lists), use copy.deepcopy().
original = ["wheat", "carrot"]
# NOT a copy β both names point to the same list
alias = original
alias.append("tomato")
print(original) # ['wheat', 'carrot', 'tomato'] β modified!
# Shallow copy β changes to top-level items don't affect original
shallow = original[:] # or original.copy() or list(original)
shallow.append("pumpkin")
print(original) # ['wheat', 'carrot', 'tomato'] β unchanged
# Deep copy for nested lists
import copy
grid_a = [[1, 2], [3, 4]]
grid_b = copy.deepcopy(grid_a)
grid_b[0][0] = 99
print(grid_a[0][0]) # 1 β unaffected
Practical: Crop Positions as a List of Tuples
A clean way to track planted crops in GrowBit is to keep a list of (x, y) tuples. This is lightweight, easy to iterate, and simple to query.
planted_positions = [(0, 0), (1, 0), (1, 2), (3, 3)]
def plant_at(x, y, positions):
if (x, y) not in positions:
positions.append((x, y))
print(f"Planted at ({x},{y})")
else:
print(f"({x},{y}) already occupied")
def harvest_at(x, y, positions):
if (x, y) in positions:
positions.remove((x, y))
print(f"Harvested ({x},{y})")
else:
print(f"Nothing at ({x},{y})")
plant_at(2, 2, planted_positions) # Planted at (2,2)
harvest_at(1, 0, planted_positions) # Harvested (1,0)
print(planted_positions)