Python Basics
Learn core Python programming concepts with in-depth technical explanations.
A list in Python is a built-in, mutable, ordered sequence of elements. It is one of the most versatile and heavily used data structures, solving the problem of needing to group multiple items together under a single variable name.
Think of a list as a flexibly-sized backpack with numbered pockets. You can put anything in it—books, pens, or even smaller backpacks. You can always check pocket #0 to see the first item, or pocket #2 for the third item, and you can freely take items out or put new items in.
# Creating and modifying a list
data = [10, "apple", 3.14]
data.append(True)
first_item = data[0]
print(data)
| Code Line | Explanation |
|---|---|
data = [10, "apple", 3.14] |
Initializes a heterogeneous list with an int, string, and float. |
data.append(True) |
Mutates the existing list by adding the boolean `True` to the very end. |
first_item = data[0] |
Uses zero-based indexing to extract the integer `10`. |
print(data) |
Outputs the final 4-element list dynamically sized in memory. |
Initial state: [10, "apple", 3.14]
Applying append(True):
Output: [10, 'apple', 3.14, True]
Transformation: The list capacity expands and a pointer to the True object is added.
Internally, Python lists are implemented as dynamic arrays (arrays of pointers) in C. When you append objects, it doesn't just add one slot; it over-allocates extra memory (e.g., jumping from capacity 4 to 8) to ensure that future appends are blazing fast (O(1) amortized time).
A Python list doesn't store the literal data side-by-side like NumPy. Instead, it stores a
continuous array of memory addresses (pointers: e.g.
0x1FFF12, 0x1FFF34). Each pointer points to a full Python object (int, str)
located randomly elsewhere in RAM.
Unlike tensors, lists only possess a 1-dimensional length natively. You calculate it using
len(my_list).
Length increases by 1 for every append(), and decreases by 1 for every
pop().
Object Type: <class 'list'>
Note: List mutation methods like append() or
sort() famously return None, modifying the object
in-place.
IndexError: Trying to access data[10] when the list only has 3
items throws an immediate error.
Negative Indexing: Python flawlessly resolves data[-1] to the
very last element without errors.
Tuples (1, 2): Use when the data should never change
(immutable).
Sets {1, 2}: Use when you need lightning-fast lookups (O(1))
and must enforce uniqueness.
Mistake: a = [1]; b = a; b.append(2). Beginners think
a is [1] and b is [1, 2]. But both are
now [1, 2] because b = a merely copies the pointer, not the list.
Fix: Use b = a.copy() to create a truly independent clone.
Appending to a list is fast, but insert(0, item) or pop(0)
(removing from the start) is incredibly slow (O(N)) because Python must shift every single
pointer one step to the left. If you need left-sided operations, import
collections.deque.
Challenge: Write code to reverse the list [1, 2, 3] permanently
without creating a new copy.
Expected Answer: my_list.reverse()
Because lists store pointers to objects rather than contiguous bytes, iterating millions of items destroys CPU Cache Locality (causing cache misses). This is exactly the bottleneck that NumPy arrays solve by dumping the pointer architecture for raw C-level memory.