Skip to content
Home Python Python List Removal During Iteration — The Skipping Bug

Python List Removal During Iteration — The Skipping Bug

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Data Structures → Topic 1 of 12
Every other item is skipped when removing list elements during iteration.
🧑‍💻 Beginner-friendly — no prior Python experience needed
In this tutorial, you'll learn
Every other item is skipped when removing list elements during iteration.
  • Python list indexing starts at 0, not 1 — the first item is always list[0], and the last is always list[-1] regardless of how long the list is.
  • sort() permanently reorders the original list and returns None — never assign its return value. Use sorted() 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.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • Python lists are ordered, mutable collections that can hold mixed data types
  • Created with square brackets [], items separated by commas
  • Indexing starts at 0; negative indices count from the end
  • Common production bug: modifying a list while iterating over it silently skips items
  • Performance insight: list comprehensions are faster than for+append loops due to internal bytecode optimisation
  • Biggest mistake: assigning the result of list.sort() to the same variable — it returns None and you lose the list
Production Incident

The Case of the Vanishing Tasks

A batch task scheduler skipped half its tasks because list removal inside a loop shifted indexes silently.
SymptomEvery other task in a processing queue was being skipped. Only tasks at even indices were executed; odd-indexed tasks were never processed.
AssumptionThe developer assumed that using list.remove() inside a for loop over the same list would eventually remove all matching tasks, one by one.
Root causeRemoving an element from a list while iterating forward shifts all subsequent elements left by one index. The loop increments the index past the element that slid into the removed slot, so it never gets visited.
FixIterate over a shallow copy of the list: for task in tasks[:]: if condition: tasks.remove(task). Or better, build a new list: tasks = [task for task in tasks if not condition].
Key Lesson
Never modify a list while iterating over it in a forward loop. Use a copy, a reverse iteration, or collect removals and apply after the loop.
Production Debug Guide

Symptom → Action guide for real-world list bugs

IndexError: list index out of rangeCheck that your index is between 0 and len(list)-1. Use negative indexing for the last element: list[-1] always works.
List modified during iteration skips itemsReplace for item in list: with for item in list[:]: to iterate over a shallow copy. Or collect items to remove in a separate list.
.sort() returns None and list seems to disappearRemember that sort() sorts in place and returns None. Use sorted(list) if you need a new sorted list. Never write list = list.sort().
append() and extend() produce unexpected nestingappend() adds its argument as a single item. extend() adds each element of an iterable individually. Verify which one you need.

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_lists.py · PYTHON
1234567891011121314151617181920212223242526
# --- 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
▶ Output
Grocery list: ['milk', 'eggs', 'bread', 'butter']
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
💡Pro Tip:
Use len(your_list) any time you need to know how many items are in a list. It works on strings, tuples and dictionaries too — one function to count them all.
📊 Production Insight
Using mutable default arguments (e.g., def f(items=[])) causes all callers to share the same list object.
Modifications inside one call unexpectedly appear in others.
Rule: default to None and instantiate a new list inside the function.
🎯 Key Takeaway
Lists are mutable — any modification inside a function affects the original list you passed in.
To protect the original, create a copy before modifying.

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.

list_indexing_and_slicing.py · PYTHON
12345678910111213141516171819202122232425262728293031323334
# 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)
▶ Output
Mercury
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']
⚠ Watch Out:
Accessing an index that doesn't exist — like planets[10] on an 8-item list — raises an IndexError. Always make sure your index is between 0 and len(list) - 1, or use negative indexes to count from the end safely.
📊 Production Insight
Slicing creates a new list; modifying it doesn't touch the original.
But if the list contains mutable objects (e.g., lists), the slice shares references to those objects.
Rule: for nested structures, use copy.deepcopy to avoid accidental cross-contamination.
🎯 Key Takeaway
Negative indices count from the end, starting at -1 for the last.
Slice [start:stop] excludes the stop — always off by one.
Use [::-1] to reverse a list in a single line.

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.

list_methods.py · PYTHON
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
# 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)
▶ Output
After append: ['buy groceries', 'call dentist', 'write report', 'call dentist', 'pay electricity bill']
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: []
🔥Interview Gold:
sort() modifies the list in place and returns None. sorted() returns a new list and leaves the original alone. Interviewers love this distinction. If you write my_list = my_list.sort(), you'll get None assigned to my_list — a classic bug.
📊 Production Insight
Using remove() inside a loop is O(n²) worst-case because each removal shifts all subsequent elements.
For large lists, build a new list with a comprehension instead.
Also: remove() raises ValueError if the item is not found — always check in first.
🎯 Key Takeaway
.sort() is in-place and returns None — never assign it.
.sorted() returns a new sorted list — use it when you need both.
.pop() both removes and returns an element — useful for stacks.

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.

looping_and_comprehensions.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738
# 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
▶ Output
All scores:
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
💡Pro Tip:
List comprehensions are not just shorter — they're measurably faster than equivalent for loops with .append() because Python optimises them internally. For transforming or filtering collections, always prefer a comprehension over a manual loop.
📊 Production Insight
List comprehensions avoid the overhead of calling list.append() repeatedly.
For a 10,000-item list, a comprehension can be 30-50% faster than a manual loop.
But beware: nested comprehensions (e.g., [expr for a in A for b in B]) can hurt readability — keep them to two levels max.
🎯 Key Takeaway
Use list comprehensions for transforming or filtering — they're faster and more readable.
enumerate(start=1) gives you a human-friendly index.
Never modify the list you're iterating over — use a comprehension instead.

Copying Lists and Shared References — Avoiding Unintended Side Effects

Assignment in Python doesn't copy the list — it copies the reference. Both variables point to the same list object. Modifying one changes the other. This is the single most common source of list-related bugs after index errors.

To make a true copy, use the list.copy() method or the slice notation list[:]. Both create a shallow copy — a new list containing references to the same objects. For flat lists (numbers, strings), that's enough. But for lists that contain mutable objects (other lists, dicts), a shallow copy still shares those inner objects. Use copy.deepcopy() from the copy module for a full independent copy.

Python also offers list() constructor as another way to shallow-copy: new_list = list(original). All three — .copy(), [:], list() — do the same thing: a shallow copy.

list_copying.py · PYTHON
12345678910111213141516171819202122232425262728
# --- Assignment copies the reference, not the list ---
original = [1, 2, 3]
b = original          # both point to the same list
b.append(4)
print("Original after b.append:", original)   # -> [1, 2, 3, 4]

# --- Shallow copy methods ---
c = original.copy()          # method 1
d = original[:]               # method 2
e = list(original)            # method 3

c.append(5)
print("Original unchanged:", original)        # still [1, 2, 3, 4]
print("c after append:", c)                   # [1, 2, 3, 4, 5]

# --- Shallow copy is not enough for nested lists ---
original_nested = [[1, 2], [3, 4]]
shallow = original_nested[:]
shallow[0].append(99)
print("Original nested:", original_nested)    # [[1, 2, 99], [3, 4]] — modified!
print("Shallow:", shallow)                   # same change

# --- Deep copy protects nested structures ---
from copy import deepcopy
deep = deepcopy(original_nested)
deep[0].append(100)
print("Original after deep copy:", original_nested)  # unchanged
print("Deep:", deep)                                 # [[1, 2, 99, 100], [3, 4]]
▶ Output
Original after b.append: [1, 2, 3, 4]
Original unchanged: [1, 2, 3, 4]
c after append: [1, 2, 3, 4, 5]
Original nested: [[1, 2, 99], [3, 4]]
Shallow: [[1, 2, 99], [3, 4]]
Original after deep copy: [[1, 2, 99], [3, 4]]
Deep: [[1, 2, 99, 100], [3, 4]]
⚠ Common Pitfall:
Default arguments like def add_item(item, my_list=[]) share the same list across all calls. Use my_list=None and create a new list inside the function to avoid unexpected accumulation.
📊 Production Insight
Shallow copy is the default and works for most cases, but nested lists hide mutation bugs.
Deep copy is O(n*m) — use it sparingly, e.g., when caching configuration objects.
Rule: if your list contains any mutable type, ask yourself whether a shallow copy is safe.
🎯 Key Takeaway
Assignment b = a does NOT copy — it creates an alias.
Use a.copy(), a[:], or list(a) for a shallow copy.
Use copy.deepcopy() for truly independent copies of nested structures.
🗂 Python List Methods at a Glance
Key mutability and return behaviours for everyday operations
Method / ActionModifies Original?ReturnsBest Used When
append(item)YesNoneAdding a single item to the end
extend(list)YesNoneMerging another list onto the end
insert(i, item)YesNoneAdding an item at a specific position
remove(item)YesNoneDeleting the first match by value
pop(index)YesThe removed itemRemoving and using the item at once
sort()Yes (in place)NonePermanently sorting the original list
sorted(list)NoNew sorted listSorting without changing the original
list[start:stop]NoNew list (slice)Extracting a portion of a list
copy()NoNew list (shallow)Creating an independent copy (flat lists)
copy.deepcopy()NoNew list (deep)Creating an independent copy of nested lists

🎯 Key Takeaways

  • Python list indexing starts at 0, not 1 — the first item is always list[0], and the last is always list[-1] regardless of how long the list is.
  • sort() permanently reorders the original list and returns None — never assign its return value. Use sorted() 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 equivalent for + append loops.
  • Assignment b = a does not copy the list. Use a.copy(), a[:], or list(a) for a shallow copy. Use copy.deepcopy() for nested structures.

⚠ Common Mistakes to Avoid

    Using `my_list = my_list.sort()`
    Symptom

    The list variable becomes None because .sort() sorts in place and returns None. You lose your data.

    Fix

    Call my_list.sort() on its own line. If you need the result assigned, use my_list = sorted(my_list).

    Modifying a list while looping over it
    Symptom

    Items are silently skipped because removing or inserting elements shifts the indexes while the loop counter moves forward.

    Fix

    Loop over a copy: for item in my_list[:] or collect items to remove and delete after the loop.

    Confusing `append()` and `extend()`
    Symptom

    Using my_list.append([a, b]) adds the entire list as a single nested element instead of merging the elements individually.

    Fix

    Use my_list.extend([a, b]) when you want each element of the iterable added individually.

    Using `list` as a variable name
    Symptom

    Overwrites the built-in list() constructor, causing TypeError: 'list' object is not callable when you later try to create a list.

    Fix

    Rename your variable to something else, e.g., my_list, items, or records.

Interview Questions on This Topic

  • QWhat is the difference between sort() and sorted() in Python, and when would you choose one over the other?JuniorReveal
    sort() is a list method that sorts the list in place and returns None. sorted() is a built-in function that returns a new sorted list from any iterable (list, tuple, string). You choose sort() when you don't need the original order and want to save memory. You choose sorted() when you need to keep the original list intact or when you're sorting a non-list iterable.
  • 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?JuniorReveal
    Indices start at 0. The first element is list[0], the second list[1], and so on. For a list of 5 items, the last positive index is list[4]. Negative indices count from the end: list[-1] is the last element, list[-2] second-to-last, etc. So list[-1] gives the same result as list[4].
  • 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?JuniorReveal
    Yes, both variables point to the same list object because assignment copies the reference, not the list. To make a true independent copy, use list_b = list_a.copy(), list_b = list_a[:], or list_b = list(list_a). For nested lists, use copy.deepcopy(list_a).
  • QWhat are the time complexities of common list operations: append, pop from end, pop from beginning, and insert at front?Mid-levelReveal
    append() and pop() from the end are O(1) amortized. pop(0) and insert(0, x) are O(n) because they shift all remaining elements. For frequent insertions/removals at the front, consider using collections.deque instead.

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.

How do I remove duplicates from a list while preserving order?

Use a loop with a set to track seen items: seen = set(); unique = [x for x in original if not (x in seen or seen.add(x))]. This builds a new list with the first occurrence of each element. If order doesn't matter, convert to a set and back: list(set(original)).

How do I flatten a list of lists into a single list?

Use a nested list comprehension: [item for sublist in list_of_lists for item in sublist]. Or use itertools.chain.from_iterable(list_of_lists). Both are efficient and Pythonic.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

Next →Tuples in Python
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged