Python Lists Explained — Creation, Indexing, Methods and Common Mistakes
Every real program manages collections of things. A music app tracks playlists. A banking app tracks transactions. A weather app tracks seven days of temperatures. Without a way to group related data together, you'd need to create a separate variable for every single item — temperature_monday, temperature_tuesday, temperature_wednesday — and your code would collapse under its own weight before it ever did anything useful. Lists are Python's answer to this problem, and they're one of the first tools every professional Python developer reaches for.
What Is a Python List and How Do You Create One?
A list in Python is an ordered, mutable collection that can hold any number of items. 'Ordered' means the items stay in the exact sequence you put them in — item 1 is always item 1. 'Mutable' means you can change the list after you create it — add items, remove items, swap them around. That flexibility is what makes lists so useful in everyday programming.
You create a list with square brackets []. Put your items inside, separated by commas. Python doesn't care what those items are — they can be numbers, text, other lists, or a wild mix of all three. Unlike some other languages, Python lists don't force you to declare a fixed size up front. You start small and grow as needed, like a shopping trolley you can always add more items to.
The empty list [] is completely valid and very common — you'll often create an empty list first and then fill it up as your program runs. Think of it as grabbing an empty basket before you start shopping.
# --- Creating different kinds of lists --- # A list of grocery items (strings) grocery_list = ["milk", "eggs", "bread", "butter"] # A list of daily temperatures in Celsius (floats) weekly_temps = [18.5, 21.0, 19.3, 22.7, 20.1, 17.8, 23.4] # A list of student ages (integers) student_ages = [14, 15, 15, 16, 14, 17] # A mixed list — Python allows different types in one list user_profile = ["Alice", 30, True, 4.9] # An empty list — created first, filled later pending_tasks = [] # Print each list to see exactly what Python stores print("Grocery list:", grocery_list) # shows all 4 items in order print("Weekly temps:", weekly_temps) # shows all 7 temperatures print("Student ages:", student_ages) # shows 6 ages print("User profile:", user_profile) # shows mixed types print("Pending tasks:", pending_tasks) # shows empty brackets # Check how many items are in a list using len() print("Number of groceries:", len(grocery_list)) # len() counts the items
Weekly temps: [18.5, 21.0, 19.3, 22.7, 20.1, 17.8, 23.4]
Student ages: [14, 15, 15, 16, 14, 17]
User profile: ['Alice', 30, True, 4.9]
Pending tasks: []
Number of groceries: 4
Accessing Items with Indexing and Slicing
Every item in a list has an address called an index. Python starts counting from 0, not 1 — so the first item is at index 0, the second at index 1, and so on. This trips up nearly every beginner at least once, so burn this into your memory: first item = index 0.
Python also gives you negative indexing, which is genuinely clever. Index -1 always refers to the last item, -2 to the second-to-last, and so on. This means you never need to calculate len(list) - 1 just to grab the last element — -1 does it instantly.
Slicing lets you extract a portion of a list using the syntax list[start:stop]. The slice starts at the start index and goes up to — but does NOT include — the stop index. It's like saying 'give me items from position 1 up to, but not including, position 4'. You can also add a step value: list[start:stop:step] to skip every other item, reverse the list, and more. Slicing always returns a brand new list — it doesn't modify the original.
# Our list of planet names in order from the sun planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] # indexes: 0 1 2 3 4 5 6 7 # neg indexes: -8 -7 -6 -5 -4 -3 -2 -1 # --- Positive indexing --- print(planets[0]) # First planet → Mercury print(planets[2]) # Third planet → Earth print(planets[7]) # Last planet → Neptune # --- Negative indexing (count from the end) --- print(planets[-1]) # Last planet → Neptune print(planets[-2]) # Second to last → Uranus print(planets[-8]) # Same as index 0 → Mercury # --- Slicing: list[start:stop] — stop is EXCLUDED --- inner_planets = planets[0:4] # indexes 0, 1, 2, 3 (not 4) print("Inner planets:", inner_planets) outer_planets = planets[4:] # from index 4 to the end print("Outer planets:", outer_planets) first_three = planets[:3] # from the start up to (not including) index 3 print("First three:", first_three) # --- Slicing with a step --- every_other = planets[::2] # start to end, jumping 2 at a time print("Every other planet:", every_other) reversed_planets = planets[::-1] # step of -1 reverses the whole list print("Reversed:", reversed_planets) # --- Slicing creates a NEW list — original is unchanged --- print("Original still intact:", planets)
Earth
Neptune
Neptune
Uranus
Mercury
Inner planets: ['Mercury', 'Venus', 'Earth', 'Mars']
Outer planets: ['Jupiter', 'Saturn', 'Uranus', 'Neptune']
First three: ['Mercury', 'Venus', 'Earth']
Every other planet: ['Mercury', 'Earth', 'Jupiter', 'Uranus']
Reversed: ['Neptune', 'Uranus', 'Saturn', 'Jupiter', 'Mars', 'Earth', 'Venus', 'Mercury']
Original still intact: ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
Modifying Lists — The Essential Built-in Methods
Because lists are mutable, Python gives you a rich toolkit for changing them. These built-in methods are like the tools on a Swiss Army knife — each one does a specific job cleanly.
append(item) adds one item to the end. insert(index, item) drops an item at a specific position, pushing everything else right. extend(another_list) merges another list onto the end — different from append, which would nest the whole second list as a single item inside the first.
For removing items: remove(item) deletes the first occurrence of a specific value. pop(index) removes the item at a given index and returns it to you — useful when you want to both remove and use the item at the same time. pop() with no argument removes the last item. clear() wipes the entire list.
For organising: sort() rearranges items in ascending order in place (it modifies the original list). reverse() flips the order in place. sorted() is a built-in function — not a method — that returns a new sorted list without touching the original.
Use index(item) to find where something lives in the list, and count(item) to see how many times something appears.
# Start with a list of tasks in a to-do app tasks = ["buy groceries", "call dentist", "write report", "call dentist"] # --- append() — add one item to the end --- tasks.append("pay electricity bill") # adds to the tail of the list print("After append:", tasks) # --- insert() — add at a specific position --- tasks.insert(1, "reply to emails") # inserts at index 1, shifts others right print("After insert at index 1:", tasks) # --- extend() — merge another list onto the end --- extra_tasks = ["book flight", "renew passport"] tasks.extend(extra_tasks) # each item of extra_tasks is added individually print("After extend:", tasks) # --- remove() — delete the first matching value --- tasks.remove("call dentist") # removes only the FIRST occurrence print("After remove 'call dentist':", tasks) # --- pop() — remove and RETURN the item at an index --- finished_task = tasks.pop(0) # removes the item at index 0 and gives it back print("Popped task:", finished_task) print("List after pop:", tasks) # --- count() — how many times does a value appear? --- duplicate_count = tasks.count("call dentist") # still one 'call dentist' left print("'call dentist' appears:", duplicate_count, "time(s)") # --- index() — find the position of a value --- position = tasks.index("write report") print("'write report' is at index:", position) # --- sort() — sorts the list IN PLACE (modifies original) --- fruit_prices = [3.50, 1.20, 4.75, 2.00, 0.99] fruit_prices.sort() # ascending order by default print("Sorted prices:", fruit_prices) # --- sorted() — returns a NEW sorted list, original untouched --- unsorted_names = ["Zara", "Alice", "Mike", "Beth"] alpha_names = sorted(unsorted_names) # original list not changed print("Sorted names (new list):", alpha_names) print("Original names unchanged:", unsorted_names) # --- reverse() — flip the list IN PLACE --- fruit_prices.reverse() print("Reversed prices:", fruit_prices) # --- clear() — empty the entire list --- temporary_cache = ["data1", "data2", "data3"] temporary_cache.clear() print("Cache after clear:", temporary_cache)
After insert at index 1: ['buy groceries', 'reply to emails', 'call dentist', 'write report', 'call dentist', 'pay electricity bill']
After extend: ['buy groceries', 'reply to emails', 'call dentist', 'write report', 'call dentist', 'pay electricity bill', 'book flight', 'renew passport']
After remove 'call dentist': ['buy groceries', 'reply to emails', 'write report', 'call dentist', 'pay electricity bill', 'book flight', 'renew passport']
Popped task: buy groceries
List after pop: ['reply to emails', 'write report', 'call dentist', 'pay electricity bill', 'book flight', 'renew passport']
'call dentist' appears: 1 time(s)
'write report' is at index: 1
Sorted prices: [0.99, 1.2, 2.0, 3.5, 4.75]
Sorted names (new list): ['Alice', 'Beth', 'Mike', 'Zara']
Original names unchanged: ['Zara', 'Alice', 'Mike', 'Beth']
Reversed prices: [4.75, 3.5, 2.0, 1.2, 0.99]
Cache after clear: []
Looping Through Lists and List Comprehensions
Looping is where lists go from a storage box to a powerhouse. The for loop in Python is designed to walk through any list naturally — you don't need to manage an index counter or worry about going out of bounds. Python handles all of that for you.
When you need both the index and the value at the same time — say, to number your output — use enumerate(). It hands you both on every iteration as a pair, which is far cleaner than manually tracking a counter variable.
List comprehensions are Python's most elegant feature for creating a new list from an existing one in a single, readable line. The pattern is [expression for item in list]. You can also add a condition: [expression for item in list if condition]. Once it clicks, you'll reach for it constantly.
Under the hood, a list comprehension is equivalent to a for loop that appends to a new list — but it's faster and more Pythonic. If you're building a new list by transforming or filtering another list, a comprehension is almost always the right choice.
# A list of exam scores for a class exam_scores = [72, 85, 91, 60, 78, 95, 55, 88, 74, 66] # --- Basic for loop --- iterate over every score print("All scores:") for score in exam_scores: print(" ", score) # each score printed on its own line # --- enumerate() — gives you index AND value together --- print("\nScores with student numbers:") for student_number, score in enumerate(exam_scores, start=1): # start=1 so counting begins at 1 print(f" Student {student_number}: {score}") # --- List comprehension: transform every item --- # Give every student a 5-point bonus boosted_scores = [score + 5 for score in exam_scores] print("\nBoosted scores:", boosted_scores) # --- List comprehension: filter items with a condition --- # Only keep scores that are a pass (70 or above) passing_scores = [score for score in exam_scores if score >= 70] print("Passing scores:", passing_scores) # --- List comprehension: transform AND filter together --- # Boost only failing scores (below 70) by 10 points adjusted_scores = [score + 10 if score < 70 else score for score in exam_scores] print("Adjusted scores:", adjusted_scores) # --- Equivalent for loop (to show what comprehension does under the hood) --- adjusted_scores_loop = [] for score in exam_scores: if score < 70: adjusted_scores_loop.append(score + 10) # failing: add 10 else: adjusted_scores_loop.append(score) # passing: keep as-is print("Same result via loop:", adjusted_scores_loop) print("Both methods match:", adjusted_scores == adjusted_scores_loop) # should be True
72
85
91
60
78
95
55
88
74
66
Scores with student numbers:
Student 1: 72
Student 2: 85
Student 3: 91
Student 4: 60
Student 5: 78
Student 6: 95
Student 7: 55
Student 8: 88
Student 9: 74
Student 10: 66
Boosted scores: [77, 90, 96, 65, 83, 100, 60, 93, 79, 71]
Passing scores: [72, 85, 91, 78, 95, 88, 74]
Adjusted scores: [72, 85, 91, 70, 78, 95, 65, 88, 74, 76]
Same result via loop: [72, 85, 91, 70, 78, 95, 65, 88, 74, 76]
Both methods match: True
| Method / Action | Modifies Original? | Returns | Best Used When |
|---|---|---|---|
| append(item) | Yes | None | Adding a single item to the end |
| extend(list) | Yes | None | Merging another list onto the end |
| insert(i, item) | Yes | None | Adding an item at a specific position |
| remove(item) | Yes | None | Deleting the first match by value |
| pop(index) | Yes | The removed item | Removing and using the item at once |
| sort() | Yes (in place) | None | Permanently sorting the original list |
| sorted(list) | No | New sorted list | Sorting without changing the original |
| list[start:stop] | No | New list (slice) | Extracting a portion of a list |
🎯 Key Takeaways
- Python list indexing starts at 0, not 1 — the first item is always
list[0], and the last is alwayslist[-1]regardless of how long the list is. sort()permanently reorders the original list and returnsNone— never assign its return value. Usesorted()when you need a new sorted list without touching the original.- Slicing (
list[start:stop]) always returns a brand-new list — it never modifies the original, making it safe for creating subsets without side effects. - List comprehensions (
[expr for item in list if condition]) are the Pythonic way to build new lists from existing ones — they're cleaner and faster than equivalentfor+appendloops.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using
my_list = my_list.sort()—.sort()sorts in place and returnsNone, so you're assigningNoneto your variable and losing the list entirely. Fix: just callmy_list.sort()on its own line, or usemy_list = sorted(my_list)if you need the result assigned. - ✕Mistake 2: Modifying a list while looping over it — removing items inside a
forloop causes Python to skip items silently because the indexes shift mid-loop. Fix: loop over a copy (for item in my_list[:]) or collect items to remove and delete them after the loop finishes. - ✕Mistake 3: Confusing
append()andextend()— callingtasks.append(["a", "b"])adds the entire list as a single nested item:[..., ['a', 'b']]. If you want the items merged in individually, usetasks.extend(["a", "b"])which gives[..., 'a', 'b'].
Interview Questions on This Topic
- QWhat is the difference between `sort()` and `sorted()` in Python, and when would you choose one over the other?
- QHow does Python list indexing work, and what is the index of the last element in a list of 5 items if you use both positive and negative indexing?
- QIf you do `list_b = list_a` and then modify `list_b`, does `list_a` change too — and why? How would you make a true independent copy?
Frequently Asked Questions
Can a Python list store different data types at the same time?
Yes — Python lists are heterogeneous, meaning a single list can hold strings, integers, floats, booleans, or even other lists all at once. For example, profile = ['Alice', 30, True, 4.9] is perfectly valid. That said, mixing types can make sorting or processing harder, so use it when it genuinely makes sense.
What is the difference between a Python list and a tuple?
Both are ordered sequences, but a list is mutable (you can change it after creation) while a tuple is immutable (once created, it cannot be modified). Lists use square brackets [] and tuples use parentheses (). Use a tuple when you have data that should never change — like GPS coordinates or RGB colour values.
How do I check if an item exists in a Python list?
Use the in keyword — it returns True or False. For example: if 'eggs' in grocery_list: checks cleanly without needing a loop. For the opposite check, use not in: if 'butter' not in grocery_list:. This is the idiomatic Python way and is very readable.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.