Back to Blog
Python Lists: Your First Data Structure Adventure

Python Lists: Your First Data Structure Adventure

July 16, 2026
Updated August 13, 2026
Reynov Christian

So far, you've stored one piece of data per variable. But what happens when you need to track 50 tasks, 200 products, or an entire class of students? That's where Python lists come in. A list is Python's most versatile container for holding multiple related items together, in one single variable.

Creating and Accessing Lists

tasks = ["Write article", "Fix VPS backup", "Reply to client"]
print(tasks[0])   # Write article
print(tasks[-1])  # Reply to client (last item)

A list is written with square brackets, and each item is separated by a comma. Lists are ordered, which means the items always stay in the sequence you put them in, and they are zero-indexed. This means the first item sits at position 0, not position 1. So tasks[0] gives you the first task, and tasks[1] gives you the second one. Negative numbers count backward from the end, so tasks[-1] always gives you the last item, no matter how long the list is, and tasks[-2] gives you the second to last item.

Essential List Methods

tasks.append("Update blog SEO")     # add to the end
tasks.insert(0, "Morning standup")  # add at a specific position
tasks.remove("Fix VPS backup")      # remove by value
tasks.sort()                        # sort alphabetically
last_task = tasks.pop()             # remove and return the last item
print(len(tasks))                   # number of items

These methods change the list directly, in place, rather than returning a new one. This is possible because lists are mutable, meaning they can be changed after they are created. Notice that pop() is a bit different from the others. It both removes an item and hands it back to you, which is why the example saves it into last_task. This is useful whenever you need to know exactly what was removed.

List Slicing

numbers = [10, 20, 30, 40, 50]
print(numbers[1:3])   # [20, 30]
print(numbers[:2])    # [10, 20]
print(numbers[2:])    # [30, 40, 50]
print(numbers[::2])   # [10, 30, 50]

Slicing lets you grab a portion of a list using [start:stop], where stop is not included in the result, the same way range() works. Leaving out start means “from the beginning”, and leaving out stop means “all the way to the end”. You can also add a third number for a step, like numbers[::2], which grabs every second item across the whole list.

Lists vs Strings

Strings behave a lot like lists of characters. Both support indexing and slicing the same way. The key difference is mutability. Strings are immutable, they can't be changed in place, while lists are mutable:

name = "Chrisnov"
print(name[0])       # C

letters = list(name)
letters[0] = "c"
print("".join(letters))  # chrisnov

Trying name[0] = "c" directly on the string would raise a TypeError, since strings can't be edited in place. The workaround shown here converts the string into a list of individual letters with list(name), changes one letter, then joins the letters back into a single string with "".join(letters). That last line reads as “join these letters together using an empty string between them”.

List Comprehensions

A list comprehension is a compact, one line way to build a new list from an existing one. It's considered the modern, “Pythonic” way to write what would otherwise take a full for loop:

numbers = [1, 2, 3, 4, 5]
squares = [n ** 2 for n in numbers]
print(squares)  # [1, 4, 9, 16, 25]

even_only = [n for n in numbers if n % 2 == 0]
print(even_only)  # [2, 4]

Both lines are shortcuts for a loop you already know how to write by hand. The first line, squares = [n ** 2 for n in numbers], means exactly the same thing as this longer version:

squares = []
for n in numbers:
    squares.append(n ** 2)

Read the comprehension from left to right: “give me n ** 2, for every n in numbers.” The second example adds a condition at the end, if n % 2 == 0, which filters out any number that isn't even before it gets added to the new list. List comprehensions are optional. A regular for loop always works too, but comprehensions are common enough in real Python code that you'll see them everywhere once you start reading other people's projects.

Practical Project: Personal Task Manager

tasks = []

while True:
    print("n1. Add task  2. View tasks  3. Complete task  4. Exit")
    choice = input("Choose an option: ")

    if choice == "1":
        new_task = input("Enter new task: ")
        tasks.append(new_task)
        print("Task added!")
    elif choice == "2":
        if not tasks:
            print("No tasks yet.")
        for index, task in enumerate(tasks, start=1):
            print(f"{index}. {task}")
    elif choice == "3":
        task_num = int(input("Which task number is done? "))
        if 1 <= task_num <= len(tasks):
            done = tasks.pop(task_num - 1)
            print(f"Completed: {done}")
    elif choice == "4":
        print("Goodbye!")
        break
    else:
        print("Invalid option, try again.")

This project combines lists, loops, and conditionals, everything from the last three articles, into one working application. A few details are worth slowing down on. The line for index, task in enumerate(tasks, start=1) uses enumerate(), a built-in function that gives you both the position and the value while looping, instead of just the value. Setting start=1 makes the count begin at 1 instead of 0, which feels more natural to a human reading a numbered list.

The completion option also deserves attention. tasks.pop(task_num - 1) subtracts 1 from the number the user typed, because the user sees task "1" as the first task, but Python's list still starts counting at index 0 behind the scenes. Forgetting that subtraction is a very common source of bugs when mixing human-friendly numbering with list indexing.

Performance Considerations

Lists are great for ordered, changeable collections. Adding an item to the end of a list with append() is fast, no matter how large the list already is. But inserting at the beginning with insert(0, ...), or searching for a specific value with in or remove(), gets slower as the list grows, because Python has to shift or check items one by one. For huge datasets or frequent lookups, other structures, like dictionaries, which we'll cover next, can be far more efficient.

Common Beginner Mistakes

  • Off by one indexing: forgetting that the first item is at index 0, not 1. This is the same idea you saw with range() in the loops article.
  • IndexError from going out of bounds: trying to access numbers[10] on a list with only 5 items raises an IndexError. Always check len() first if you're not sure the index exists.
  • Trying to edit a string like a list: code like name[0] = "c" raises a TypeError, since strings are immutable. Convert to a list first if you need to edit individual characters.
  • Confusing remove() and pop(): remove(value) takes the actual value you want gone. pop(index) takes a position. Mixing these up raises a ValueError or removes the wrong item.

Try It Yourself

Practice these three short exercises in a new file called practice_lists.py before moving on:

  1. Create a list of 5 of your favorite foods, then print only the first and last item using indexing.
  2. Write a list comprehension that takes a list of numbers from 1 to 20 and produces a new list containing only the numbers divisible by 3.
  3. Add a 5th menu option to the Personal Task Manager that lets the user clear all tasks at once. Hint: an empty list looks like [].

Frequently Asked Questions

What's the difference between append() and insert()?

append() always adds an item to the end. insert(position, item) lets you place an item at any specific index you choose.

Can a list hold different data types together?

Yes. A single Python list can freely mix strings, numbers, booleans, and even other lists, all in the same list.

Why did I get an IndexError?

This happens when you try to access an index that doesn't exist, like numbers[10] on a 5 item list. Always check len() before accessing by index in situations where the size might change.

What's the difference between remove() and pop()?

remove(value) deletes the first matching value it finds. pop(index) deletes by position and also returns the removed item, which is useful when you need to use that value right after removing it.

Do I have to use list comprehensions?

No. A regular for loop with append() always works and is often easier to read while you're still learning. Use comprehensions once they start feeling natural.

Ready to organize your code itself, not just your data? In our next article, we'll explore Python functions. You'll learn how to package repeated logic into clean, reusable blocks instead of copying and pasting code. You'll build a small library of utility functions you can reuse across every project from here on.

Learning programming works best as a collaborative process, and your questions and insights help make these tutorials more effective for everyone. If you get stuck with list indexing, run into an IndexError you can't explain, or discover an interesting feature to add to the task manager project, please share your experience in the comments. Your feedback helps create a learning community where we can all benefit from each other's discoveries and solutions.

You Might Also Like

1 Comment

U
umekAugust 2, 2026

tes komen e, Kaks

Leave a Comment

0/5000