Skip to content
C Codeloom
Python

Python Lists: Create, Slice, Mutate, Iterate

A complete beginner's guide to Python lists — creation, indexing, slicing, mutation, the essential methods, iteration patterns, and a first look at list comprehensions.

·8 min read · By Yash Kesharwani
Beginner 10 min read

What you'll learn

  • Every way to create a Python list
  • How indexing and slicing work on lists
  • The list methods you will use constantly
  • How to iterate over lists cleanly
  • A first look at list comprehensions
  • Why "copying" a list matters and how to do it safely

Prerequisites

  • Familiarity with Python data types — see Data Types

A list is an ordered, mutable collection of values. It is the most-used container type in Python, and once you are comfortable with lists, large parts of the standard library and most third-party data tools become much easier.

Creating a list

The most common way to create a list is with square brackets:

fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
empty = []

A list can hold values of any type, including a mix:

mixed = [1, "two", 3.0, True, None]

Mixing types is technically legal but rare in good code — most lists hold values of the same kind.

You can also create a list from any iterable using the list() constructor:

chars = list("hello")        # ['h', 'e', 'l', 'l', 'o']
nums = list(range(5))        # [0, 1, 2, 3, 4]

Indexing and length

Lists use zero-based indexing, just like strings, and support negative indices that count from the end:

fruits = ["apple", "banana", "cherry"]
print(fruits[0])     # apple
print(fruits[2])     # cherry
print(fruits[-1])    # cherry
print(len(fruits))   # 3

An index outside the valid range raises IndexError.

Lists are mutable

Unlike strings and tuples, lists can be modified in place. You can reassign individual elements:

fruits = ["apple", "banana", "cherry"]
fruits[1] = "blueberry"
print(fruits)        # ['apple', 'blueberry', 'cherry']

This mutability is exactly why lists are the workhorse container — almost any data-processing task involves transforming a list as you go.

Slicing

The slicing syntax you learned for strings works identically on lists:

nums = [10, 20, 30, 40, 50]
print(nums[1:4])     # [20, 30, 40]
print(nums[:3])      # [10, 20, 30]
print(nums[2:])      # [30, 40, 50]
print(nums[::-1])    # [50, 40, 30, 20, 10]   — reversed
print(nums[::2])     # [10, 30, 50]           — every second element

A slice always returns a new list — the original is unchanged.

Adding and removing items

The everyday mutation methods you’ll use most often:

fruits = ["apple", "banana"]

fruits.append("cherry")           # add to the end
print(fruits)                     # ['apple', 'banana', 'cherry']

fruits.insert(1, "blueberry")     # insert at index 1
print(fruits)                     # ['apple', 'blueberry', 'banana', 'cherry']

fruits.remove("banana")           # remove first matching value
print(fruits)                     # ['apple', 'blueberry', 'cherry']

last = fruits.pop()               # remove & return last element
print(last, fruits)               # cherry ['apple', 'blueberry']

first = fruits.pop(0)             # remove & return element at index 0
print(first, fruits)              # apple ['blueberry']

fruits.clear()                    # remove everything
print(fruits)                     # []

append and pop together turn a list into a stack. pop(0) removes from the front (less efficient — for queue-like patterns prefer collections.deque).

Concatenation and extension

Two ways to combine lists:

a = [1, 2, 3]
b = [4, 5, 6]

c = a + b              # creates a new list
print(c)               # [1, 2, 3, 4, 5, 6]

a.extend(b)            # adds elements of b to a in place
print(a)               # [1, 2, 3, 4, 5, 6]

extend is preferable inside loops because it doesn’t allocate a new list each time.

Searching and counting

nums = [10, 20, 30, 20, 40]
print(nums.index(20))     # 1   — index of first match (raises if absent)
print(nums.count(20))     # 2
print(20 in nums)         # True
print(99 in nums)         # False

Sorting and reversing

nums = [3, 1, 4, 1, 5, 9, 2, 6]
nums.sort()
print(nums)               # [1, 1, 2, 3, 4, 5, 6, 9]

nums.sort(reverse=True)
print(nums)               # [9, 6, 5, 4, 3, 2, 1, 1]

nums.reverse()
print(nums)               # [1, 1, 2, 3, 4, 5, 6, 9]

sort() modifies the list in place and returns None. If you need a sorted copy without modifying the original, use the built-in sorted():

nums = [3, 1, 2]
new = sorted(nums)
print(nums, new)          # [3, 1, 2] [1, 2, 3]

sort() accepts a key argument to sort by something other than the value itself:

words = ["pear", "apricot", "fig"]
words.sort(key=len)
print(words)              # ['fig', 'pear', 'apricot']

Iterating over a list

The most direct way to iterate is for ... in:

fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

When you need both the index and the value, use enumerate():

for i, fruit in enumerate(fruits):
    print(i, fruit)
# 0 apple
# 1 banana
# 2 cherry

To iterate two lists in parallel, use zip():

names = ["Alice", "Bob", "Carol"]
ages = [30, 25, 35]
for name, age in zip(names, ages):
    print(f"{name} is {age}")

These three patterns — for, enumerate, and zip — handle the overwhelming majority of list iteration in real code.

Try it yourself. Build a list of the first ten square numbers (1, 4, 9, ..., 100) using a for loop and the append method. Then print each one with its index using enumerate.

List comprehensions

A list comprehension is a compact way to build a list from another iterable. The form is [expression for item in iterable].

# Traditional
squares = []
for n in range(1, 6):
    squares.append(n * n)

# Comprehension — exactly the same result
squares = [n * n for n in range(1, 6)]
print(squares)            # [1, 4, 9, 16, 25]

You can add an if filter:

evens = [n for n in range(10) if n % 2 == 0]
print(evens)              # [0, 2, 4, 6, 8]

Comprehensions are a hallmark of idiomatic Python. They are not just shorter — they are also typically faster than the equivalent loop. We will return to them in depth later in the series.

Copying a list

Assigning a list to another variable does not copy it. Both names point to the same underlying object:

a = [1, 2, 3]
b = a
b.append(4)
print(a)    # [1, 2, 3, 4]   — surprise!

When you actually want a copy, use one of these:

b = a.copy()       # method (Python 3.3+)
b = list(a)        # constructor
b = a[:]           # full slice

All three produce a shallow copy — a new outer list, but the inner objects (if any) are still shared. For nested lists, use copy.deepcopy() from the standard library:

import copy
b = copy.deepcopy(a)

This distinction trips up nearly every beginner. Spend a minute experimenting with it in the REPL until it clicks.

Try it yourself. Create a list of three names. Assign it to a second variable using =, modify the second variable, and observe what happens to the first. Then repeat using .copy() and confirm the original stays intact.

A small worked example

Putting it all together — read a list of scores, compute the average, find the highest, and list the names of everyone who passed.

names = ["Alice", "Bob", "Carol", "Dave", "Eve"]
scores = [82, 47, 91, 56, 73]

# Average
average = sum(scores) / len(scores)

# Highest
top = max(scores)
top_name = names[scores.index(top)]

# Passers (>= 60)
passers = [name for name, score in zip(names, scores) if score >= 60]

print(f"Average: {average:.1f}")
print(f"Top scorer: {top_name} with {top}")
print(f"Passers: {passers}")

Every concept in this post appears in those eight lines. This is the standard texture of real Python.

Recap

You now know:

  • Lists are ordered, mutable collections created with [] or list()
  • Indexing and slicing work the same way as on strings
  • append, insert, remove, pop, extend, clear are the everyday mutation methods
  • sort, sorted, and reverse handle ordering — sort mutates, sorted returns a new list
  • for, enumerate, and zip cover virtually all iteration patterns
  • List comprehensions are the idiomatic way to build a list from another iterable
  • Assigning a list does not copy it — use .copy(), list(...), or [:]

Next steps

The next post is the natural counterpart to this one: tuples — Python’s immutable cousin of the list. We’ll cover when to choose a tuple over a list, the surprising single-element gotcha, and tuple packing and unpacking.

→ Next: Python Tuples and When to Use Them

Questions or feedback? Email codeloomdevv@gmail.com.