AlgoMaster Logo

Lists Basics

High Priority23 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A list is Python's everyday container for "a bunch of things in a specific order". A shopping cart, the items in an order, a customer's recently viewed products, the prices on a receipt: every one of those is a list. This lesson covers what a list actually is, the two ways to create one, how to reach into a list to read or replace an element, and the small set of pitfalls that catch beginners on day one.

What a List Is

A list is an ordered, mutable sequence of values. Three words, each one doing real work.

Ordered means the position of every item matters and stays put. The first item you put in is the first item you'll find when you look. If you write ["Mouse", "Cable", "HDMI"], that's the order Python remembers, and it won't shuffle behind your back.

Mutable means you can change a list after creating it. Add an item, replace an item, remove an item, all in place. The list keeps the same identity; only its contents change. This is the big difference from strings. A string like "hello" can never be modified; any operation that looks like it changes a string actually builds a new one. Lists let you change them directly.

Sequence means the items live at numbered positions you can address. Position 0 is the first item, position 1 is the second, and so on. That numbering is what lets you ask "give me the third item" without scanning the whole list.

Here's a list in its simplest form:

Three items, stored in the order you wrote them. Python prints lists with square brackets and comma-separated values, which happens to be exactly how you write them in source code. Lists are usually what you reach for when you have a small-to-medium collection of items that belongs together and whose order matters.

Creating Lists

There are two common ways to make a list, and a couple of variations worth knowing.

The first is a list literal: square brackets with comma-separated values inside. This is what you'll write most of the time.

[] is an empty list, ready to have items added later. A list with one item still needs the brackets; "Wireless Mouse" on its own is just a string, not a list. The trailing comma on the last item is allowed but optional:

The trailing comma is a small convenience: it makes future edits cleaner because adding a new line doesn't require remembering to add a comma to the previous line. Most Python codebases adopt this style for multi-line literals.

The second way is the `list()` constructor. Called with no arguments, it gives you an empty list:

list() and [] produce the same empty list. Most Python developers prefer [] because it's shorter and reads as "an empty list" at a glance. The constructor earns its keep when you pass it an iterable (anything Python can walk through one item at a time, which you saw in the for-loop lesson). list(iterable) builds a new list from those items:

list("HDMI") walks the string one character at a time and packs each character into the new list. list(range(5)) materializes a range into a real list of integers. list(["Mouse", "Cable"]) creates a fresh list with the same items as the original.

A side note that trips people up: list("Mouse") and ["Mouse"] are different. The first walks the string and gives you a list of single characters. The second wraps the whole string in a list as a single item.

Pick the literal [] syntax for everyday list creation. Reach for list(...) when you have an existing iterable (a string, a range, a generator) that you want to materialize into a real list.

Lists Can Hold Mixed Types

Unlike arrays in some other languages, a Python list doesn't care whether all the items are the same type. You can mix strings with numbers, booleans with floats, even put lists inside lists.

A name (string), an age (int), an email (string), and an "is a premium member" flag (bool), all sitting in one list. Python is happy with this.

You can even put a list inside another list. Here a cart holds three line items, where each line item is itself a small list of [name, price, quantity]:

This kind of nested structure is common when you outgrow a flat list but don't yet need a dictionary.

Mixing types is allowed, but it isn't always a good idea. A list of products and prices jammed together makes code harder to read than two parallel lists, or one list of small structured records. The rule of thumb: a list usually holds items of the same kind, where "kind" is judged by how you use them, not by their type. A cart of product names is a clean list. A list that happens to contain a name, a price, and a stock count is a record, and it usually wants a named structure (a dictionary or a class) once it grows.

Indexing: Getting Items by Position

Items in a list live at numbered positions. The first item is at position 0, the second at 1, the third at 2, and so on. You read an item with the bracket syntax cart[index].

Zero-based indexing means the highest valid index is len(cart) - 1, not len(cart). A four-item cart has positions 0 through 3. The position numbering for that cart looks like this:

The top row is the positive indices: how Python numbers items from the front. The bottom row is the negative indices: how Python numbers items from the back. We'll get to those next, but it helps to see both numbering schemes side by side now so the picture is clear.

Indexing is a constant-time operation. Whether your list has 10 items or 10 million, cart[5000] takes the same amount of time as cart[0]. That's a property of how Python stores lists in memory (a contiguous block of references), and it's the main reason lists are the default sequence in Python.

Negative Indexing: Counting From the End

Python lets you index from the back of the list using negative numbers. cart[-1] is the last item, cart[-2] is the second-to-last, and so on.

Negative indexing is one of the small features that makes Python pleasant to write. Anywhere you'd reach for cart[len(cart) - 1] to get the last item, you can just write cart[-1]. It's shorter, harder to get wrong (no off-by-one), and the intent is obvious from the syntax.

The relationship between positive and negative indices for a four-item list is:

ItemPositive IndexNegative Index
"Wireless Mouse"0-4
"USB Cable"1-3
"HDMI Cable"2-2
"Bluetooth Speaker"3-1

A useful identity: for any valid index i, the negative index that points to the same item is i - len(cart). So cart[0] and cart[-4] are the same item in a four-item list, cart[1] and cart[-3] are the same, and so on.

Negative indexing is also O(1). Python doesn't walk from the end; it converts the negative index to a positive one internally (by adding the length) and jumps directly to the slot.

When the Index Is Out of Range

Asking for an index past either end of the list raises IndexError. There's no silent "return None" or "wrap around". Python tells you loudly:

The cart has three items, so the valid positive indices are 0, 1, and 2. Asking for 5 doesn't make sense, and Python refuses to guess what you meant.

Negative indices have the same boundary, just on the other side. The smallest valid negative index for a three-item list is -3. Going further raises the same error:

Loud failure is the right behavior here. Silent return of None for out-of-range access would let bugs hide. With IndexError, the line that's wrong is the line that crashes.

If you actually want a "default if missing" lookup, the standard pattern is to check the length first, or wrap the access in try/except. The first is cleaner when the check is cheap and obvious:

For looking up the last item of a possibly-empty list, the check is even simpler: if the list is truthy, it has at least one item, so cart[-1] is safe.

Mutating a List: Assigning by Index

You can replace an item in a list by assigning to its index. The list itself stays the same object; only the contents change.

The customer changed their mind, so the item at position 1 is now "Ethernet Cable" instead of "USB Cable". The list is the same list it was before; the value at index 1 is what changed. Negative indices work for assignment too:

That's the headline feature of mutability: you can update items in place without rebuilding the list. Contrast this with strings, which are immutable. The same pattern on a string fails:

Python refuses to let you change a string in place. To "change" a string, you build a new one and rebind the name (product = "F" + product[1:], for example). Lists don't have that restriction. They were designed to be modified.

Index assignment is also O(1). Replacing the value at a position is the same cost as reading it; Python jumps to the slot and overwrites the reference stored there.

Out-of-range assignment fails the same way as out-of-range reading:

Index assignment only replaces an existing slot. It doesn't create new positions. To add items to a list, use methods like append and insert.

Length With len()

The len() built-in returns the number of items in a list. It's the most common thing you'll ask about a list after creating one.

len() works on any sequence (lists, strings, tuples) and on most other collections. It returns an integer, never None, never raises on a normal list.

The cost is O(1) regardless of list size. Python stores the length as part of the list object and updates it whenever the list changes, so len() just reads that stored number. You can call len() on a million-item list a million times and the total cost is trivial.

One place this matters: when you want to walk every valid index of a list, range(len(cart)) produces exactly those indices. We saw in the for-loop lesson that enumerate() is usually the better choice when you want both index and value, but range(len(...)) still earns its keep when you only need indices, like assigning new values to specific positions.

The loop uses each index to overwrite the slot in place. List comprehensions offer a cleaner way to write this, but the index-based version is worth seeing first because it makes the mutation obvious.

Empty Lists and Truthiness

An empty list is a valid, useful thing. A brand-new cart has zero items. A search returned no results. A customer's wishlist hasn't been started yet. All of these are real states your program needs to handle.

Python treats an empty list as falsy in a boolean context, and a non-empty list as truthy. That means you can check whether a list has items without calling len():

Adding an item flips the result:

Writing if cart: is the Pythonic way to ask "does this list have anything in it?". It reads naturally and avoids the slightly-verbose if len(cart) > 0:. Both produce the same result for a list, but the shorter form is the one Python developers prefer, and it works for other collection types too (strings, dicts, sets) with the same meaning.

This idiom pairs nicely with the "get the last item" pattern from earlier. Once you know a list is truthy, list[-1] is safe:

Without the check, recent_views[-1] on an empty list would raise IndexError. The truthiness check is the cheap, idiomatic guard that prevents that crash.

What's Wrong With This Code?

One of the most common beginner bugs is mixing up where a list "ends" with off-by-one indexing. Here's the trap:

len(cart) is 3, but the valid indices are 0, 1, and 2. Asking for cart[3] is asking for a fourth item that doesn't exist. The fix is to subtract one, or just use a negative index:

The negative-index form is the one you'll see in real Python code. It's shorter, harder to get wrong, and works the same whether the list has 3 items or 30,000.

A related bug is going past the start of the list with a negative index:

A three-item list has negative indices -1, -2, and -3. Going to -4 walks past the first item. The rule is symmetric: for a list of length n, the valid indices run from -n to n - 1. Anything outside that range raises IndexError.

The fix depends on the situation. If the index comes from user input or an unknown source, validate the range before indexing. If it's computed in your own code, the bug is upstream: somewhere a calculation is producing the wrong index, and the right fix is to correct the calculation, not to silently mask the error.

Quiz

Lists Basics Quiz

10 quizzes