---
title: Lists in Python
source: https://app.sythra.ai/learn/python/lists-in-python
topic: Python
updated: 2026-08-12
publisher: Sythra (https://app.sythra.ai)
---

# Lists in Python

A Python list is a mutable, ordered sequence that can hold values of any type. Unlike strings, you can change elements in place, and methods like .append(), .sort(), and .pop() modify the list directly — but assigning one list to another creates an alias, not a copy.

_Source: [https://app.sythra.ai/learn/python/lists-in-python](https://app.sythra.ai/learn/python/lists-in-python) — free to read on Sythra._

## Key points

- Lists are mutable — unlike strings, you can change elements in place
- Index and slice lists the same way as strings, but slices can be assigned to
- .append(), .extend(), .sort() modify in place and return None
- Map, filter, and reduce are the core patterns for processing lists
- b = a creates an alias (same object), not a copy — use b = a[:] for a real copy
- Passing a list to a function passes a reference, not a copy

You already know a string is a sequence of characters you can walk through and slice apart. Now imagine a sequence that can hold _anything_ — numbers, strings, even other lists tucked inside it — and that you can actually **change** after creating it. That is a **list**, Python's most flexible and most-used data structure.

This page covers creating and indexing lists, the mutability that sets them apart from strings, the map/filter/reduce patterns, and **aliasing** — the subtle trap that causes some of the most confusing bugs beginners run into. Pair it with [Strings in Python](/learn/python/strings-in-python) for the sequence operations they share.

## What you will learn

By the end you can:

- Create lists and index into them, including nested lists
- Change, add, and delete elements — something strings can never do
- Use `+`, `*`, slicing, and core list methods
- Recognize the **map**, **filter**, and **reduce** patterns
- Explain **aliasing** and why `b = a` is not a copy

## A list is a sequence — but way more flexible than a string

A list is an **ordered collection of values**. The individual values are called **elements** or **items**, and they can be _any_ type — integers, floats, strings, even other lists. Create one with square brackets, values separated by commas:

```python
[10, 20, 30, 40]
['crunchy frog', 'ram bladder', 'lark vomit']
```

A list does not have to hold values of the same type — this is completely valid:

```python
['spam', 2.0, 5, [10, 20]]
```

Notice the last item is itself a list — a **nested list**. Totally legal, occasionally confusing to read, genuinely useful once you get used to it. An **empty list** is just two brackets with nothing between them:

```python
empty = []
cheeses = ['Cheddar', 'Edam', 'Gouda']
numbers = [17, 123]
print(cheeses, numbers, empty)
# ['Cheddar', 'Edam', 'Gouda'] [17, 123] []
```

## Lists are mutable

This is the big deal about lists: unlike strings, you can **change them after creating them** — that property is called being **mutable**. You access elements exactly like strings, with square brackets starting at index `0`:

```python
cheeses = ['Cheddar', 'Edam', 'Gouda']
print(cheeses[0])   # Cheddar
```

But here is what strings could never do — put the bracket on the **left** of `=` and directly change an element:

```python
numbers = [17, 123]
numbers[1] = 5
print(numbers)   # [17, 5]
```

The list has actually been modified — this is called modifying something **in place**. List indices behave like string indices in every other way: whole-number expressions work as indices, negative indices count from the end, and an out-of-range index raises `IndexError`. The `in` operator also works the same way:

```python
cheeses = ['Cheddar', 'Edam', 'Gouda']
'Edam' in cheeses    # True
'Brie' in cheeses    # False
```

## Traversing a list

A `for` loop is the most common way to walk through a list — same syntax as strings:

```python
for cheese in cheeses:
    print(cheese)
```

That works great for _reading_ each element. To **change** elements as you go, you need the index too — combine `range()` and `len()`:

```python
for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2
```

`range(len(numbers))` produces indices `0, 1, 2, ...` up to the length minus one. On each pass, `i` lets you both read and write the same spot.

> **Two details worth remembering:** 

## List operators: + and *

Just like strings, `+` joins and `*` repeats. The `+` operator joins two lists into a brand-new one:

```python
a = [1, 2, 3]
b = [4, 5, 6]
c = a + b
print(c)   # [1, 2, 3, 4, 5, 6]
```

`*` repeats an entire list a given number of times:

```python
[0] * 4         # [0, 0, 0, 0]
[1, 2, 3] * 3   # [1, 2, 3, 1, 2, 3, 1, 2, 3]
```

`[0] * n` is a fast way to build a list of `n` zeros.

## List slices

Just like strings, you can pull out a chunk using a slice:

```python
t = ['a', 'b', 'c', 'd', 'e', 'f']
t[1:3]   # ['b', 'c']
t[:4]    # ['a', 'b', 'c', 'd']
t[3:]    # ['d', 'e', 'f']
t[:]     # ['a', 'b', 'c', 'd', 'e', 'f']  (a full copy!)
```

Here is something lists can do that strings genuinely cannot — use a slice on the **left** side of an assignment, replacing several elements at once:

```python
t = ['a', 'b', 'c', 'd', 'e', 'f']
t[1:3] = ['x', 'y']
print(t)   # ['a', 'x', 'y', 'd', 'e', 'f']
```

`t[:]` is worth remembering on its own — it creates a fully independent copy of a list. Since lists are mutable, copying before you modify one is a good habit whenever you want the original left untouched (more on exactly why, below in aliasing).

## List methods

`.append()` adds exactly one new element to the end:

```python
t = ['a', 'b', 'c']
t.append('d')
print(t)   # ['a', 'b', 'c', 'd']
```

`.extend()` takes an entire other list and adds all of its elements onto the end:

```python
t1 = ['a', 'b', 'c']
t2 = ['d', 'e']
t1.extend(t2)
print(t1)   # ['a', 'b', 'c', 'd', 'e']
print(t2)   # ['d', 'e']  (unchanged)
```

`.sort()` rearranges the elements from low to high:

```python
t = ['d', 'c', 'e', 'b', 'a']
t.sort()
print(t)   # ['a', 'b', 'c', 'd', 'e']
```

> **The trap that catches nearly everyone:** 

## Map, filter, and reduce

Three patterns come up constantly once you work seriously with lists. Once you recognize each by name, you will spot them everywhere in real code.

### Reduce — combining every element into one value

A **reduce** walks through a list and combines everything into one final result:

```python
def add_all(t):
    total = 0
    for x in t:
        total += x
    return total
```

`total += x` is shorthand for `total = total + x`. `total` is called an **accumulator** — it builds up the answer as the loop runs. This pattern is so common Python has it built in:

```python
t = [1, 2, 3]
sum(t)   # 6
```

### Map — applying a function to every element

A **map** transforms every element and builds a new list from the results:

```python
def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res
```

`res` starts empty, and each pass appends a transformed version of the current item.

### Filter — keeping only elements that pass a test

A **filter** selects only the elements that pass some test:

```python
def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res
```

`.isupper()` returns `True` only when every letter is uppercase. Almost any list-processing code you write ends up being some combination of map, filter, and reduce — recognizing which one you are using makes your own thinking clearer.

## Deleting elements — three ways

**`.pop()`** removes by index and hands the value back:

```python
t = ['a', 'b', 'c']
x = t.pop(1)
print(t)   # ['a', 'c']
print(x)   # 'b'
```

Called with no argument, `.pop()` removes and returns the last element instead.

**`del`** removes by index when you do not need the value — notice it is an operator, not a method (no dot):

```python
t = ['a', 'b', 'c']
del t[1]
print(t)   # ['a', 'c']

t = ['a', 'b', 'c', 'd', 'e', 'f']
del t[1:5]
print(t)   # ['a', 'f']
```

**`.remove()`** removes by value, not index — it finds the _first_ match and deletes it, returning `None`:

```python
t = ['a', 'b', 'c']
t.remove('b')
print(t)   # ['a', 'c']
```

Rule of thumb: use `.pop()` when you need the removed value back, `del` when you know the index and do not care about the value, and `.remove()` when you know the value but not the index.

## Converting between lists and strings

A string is a sequence of characters; a list is a sequence of values — close cousins, not the same thing. `list()` breaks a string into a list of characters:

```python
s = 'spam'
t = list(s)
print(t)   # ['s', 'p', 'a', 'm']
```

> **Naming warning:** 

`.split()` breaks a string into a list of words at whitespace:

```python
s = 'pining for the fjords'
t = s.split()
print(t)   # ['pining', 'for', 'the', 'fjords']

s = 'spam-spam-spam'
s.split('-')   # ['spam', 'spam', 'spam']  (custom delimiter)
```

`.join()` reverses `.split()` — call it **on** the delimiter, passing the list as the argument:

```python
t = ['pining', 'for', 'the', 'fjords']
delimiter = ' '
delimiter.join(t)   # 'pining for the fjords'

''.join(['a', 'b', 'c'])   # 'abc'
```

## Objects, values, and identity

Consider two variables holding the same string. Do they point to the same object, or two separate ones that happen to match? Python quietly optimizes strings, so they end up pointing to the exact same object:

```python
a = 'banana'
b = 'banana'
a is b   # True — literally the same object
```

Lists are different — Python deliberately creates a brand-new object every time:

```python
a = [1, 2, 3]
b = [1, 2, 3]
a is b   # False — different objects, same values
```

Two lists with the same elements are **equivalent** but not **identical**. `==` checks equivalence (same value); `is` checks identity (literally the same object). This distinction matters enormously once you start modifying lists.

## Aliasing — the hidden trap

Assigning one list variable to another makes both names point to the **same underlying object**:

```python
a = [1, 2, 3]
b = a
b is a   # True
```

This is called **aliasing** — one object, two names. Because lists are mutable, changing the list through _either_ name changes what you see through the other:

```python
b[0] = 17
print(a)   # [17, 2, 3]
```

You only touched `b`, but `a` changed too — because `a` and `b` were never two separate lists, just two names for the same one. This is not a bug; it is how Python is designed to work. But it remains one of the most common sources of confusing bugs for beginners and experienced programmers alike. For a genuinely independent copy, use `b = a[:]`.

Strings never run into this — since they are immutable, there is nothing that could get accidentally modified through an alias.

## Passing lists to functions

When you pass a list into a function, the function receives a **reference** to that same list — not a fresh copy. Changes made inside are visible outside once the function returns:

```python
def delete_head(t):
    del t[0]

letters = ['a', 'b', 'c']
delete_head(letters)
print(letters)   # ['b', 'c']
```

Inside the function, `t` is simply an alias for `letters`. But there is a genuine trap: some operations modify a list in place, while others build and return a brand-new list — and mixing these up breaks code silently:

```python
t1 = [1, 2]
t2 = t1.append(3)
print(t1)   # [1, 2, 3]
print(t2)   # None — append always returns None

t3 = t1 + [4]
print(t3)   # [1, 2, 3, 4] — a separate, new list
```

Here is a classic mistake — a function that _looks_ like it removes the first element but does nothing to the caller's list:

```python
def bad_delete_head(t):
    t = t[1:]    # WRONG — builds a new list, reassigns only the LOCAL t
```

`t[1:]` builds an entirely new list, and the assignment only redirects the local variable — it never touches the original outside the function. The correct approach, when you want a modified _copy_ and want the original untouched, is to `return` the new list instead:

```python
def tail(t):
    return t[1:]

letters = ['a', 'b', 'c']
rest = tail(letters)
print(rest)      # ['b', 'c']
print(letters)   # ['a', 'b', 'c']  (untouched)
```

## Common mistakes

- Writing `t = t.sort()` — void methods return `None`, so this throws the list away
- Writing `t.append([x])` when you meant `t.append(x)` — the first nests a list instead of adding `x`
- Writing `t = t + x` instead of `t = t + [x]` — `+` needs a list on both sides
- Assuming `b = a` makes an independent copy — it creates an alias; use `b = a[:]` for a real copy
- Reassigning a function parameter (`t = t[1:]`) expecting it to change the caller's list

## Why this matters

Lists are everywhere in real Python programs — collecting results, processing data, passing structured information between functions. Understanding mutability and aliasing early prevents the exact class of bug where "my list changed and I have no idea why" — usually because two names were pointing at the same object all along.

> **Practice with Sythra:**  [Practice with AI tutor](https://app.sythra.ai/pricing)

## FAQ

### What is the difference between a list and a string in Python?

A string is an immutable sequence of characters. A list is a mutable, ordered sequence that can hold any type of value, including other lists, and can be changed after creation.

### Why does t = t.sort() delete my list in Python?

sort() is a void method — it rearranges the list in place and returns None. Assigning the result back to t replaces your list with None. Just call t.sort() on its own line, without reassigning.

### What is aliasing in Python lists?

Aliasing happens when two variables point to the same list object, e.g. b = a. Because lists are mutable, changing the list through either name affects both. Use b = a[:] to make an independent copy instead.

### What is the difference between append() and extend() in Python?

append() adds its argument as a single new element to the end of the list. extend() takes another list and adds all of its elements individually onto the end.

### How do you remove an item from a list in Python?

Use pop(index) to remove by index and get the value back, del list[index] to remove by index without needing the value, or remove(value) to remove the first matching value.

### What are map, filter, and reduce in Python?

Map transforms every element of a list into a new list. Filter keeps only elements that pass a test. Reduce combines every element into a single result, like summing a list.

## Related

- [Strings in Python](https://app.sythra.ai/learn/python/strings-in-python) — The sequence operations lists and strings share — and where they differ.
- [Data Types in Python](https://app.sythra.ai/learn/python/data-types) — Where list fits among Python's mutable, non-primitive types.
- [Operators and Operands in Python](https://app.sythra.ai/learn/python/operators-operands) — How + and * behave differently on lists versus numbers.
- [The input() Function in Python](https://app.sythra.ai/learn/python/python-input-function) — Combine input() with .split() to read multiple values at once.
- [Dictionaries in Python](https://app.sythra.ai/learn/python/dictionaries-in-python) — Key-value pairs, the histogram pattern, and memoization.
- [Tuples in Python](https://app.sythra.ai/learn/python/tuples-in-python) — Immutability, unpacking, *args, zip(), and DSU sorting.
- [Sets in Python](https://app.sythra.ai/learn/python/sets-in-python) — Uniqueness, fast membership checks, and set math.
- [Python course hub](https://app.sythra.ai/learn/python) — All free Python explainers and the path into Agentic practice.
- [The for Loop in Python](https://app.sythra.ai/learn/python/for-loop-python) — Looping over ranges, strings, and lists with for.

---

Written by Sythra — Learn machine learning by building. Practice this topic with Sythra's AI tutor: https://app.sythra.ai/pricing
