Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›Lists — Creation and Operations

Data Structures

Lists — Creation and Operations

Lists are versatile, ordered collections that store heterogeneous items in a single mutable structure. They are fundamental in Python for managing sequential data, enabling dynamic resizing and efficient element access. You should use them whenever you need to maintain an ordered sequence of objects that might need to be modified, sorted, or filtered during program execution.

Creating and Initializing Lists

In Python, a list is defined by enclosing comma-separated values within square brackets. Because Python is dynamically typed, a list can contain objects of entirely different types, such as integers, strings, or even other lists, simultaneously. When you create a list, the interpreter allocates a contiguous block of memory to store references to these objects, not the objects themselves. This architecture is crucial because it allows the list to remain flexible in size; as you add items, Python handles the memory reallocation behind the scenes. Understanding that a list stores references means you realize that assigning a list to a new variable name does not create a copy of the list, but rather creates a new reference to the same underlying memory location. This foundational behavior is essential for debugging object mutation.

# A list containing mixed data types
user_data = [101, "Alice", 88.5, ["Python", "SQL"]]

# Creating an empty list and appending later
active_sessions = []
active_sessions.append("session_001")

Accessing and Slicing Elements

Accessing elements in a list is performed using zero-based indexing, where the first element is at index zero. Python provides a powerful feature called slicing, which allows you to extract sub-sequences by specifying a start, stop, and optional step value. When you define a slice like [start:stop], the start index is inclusive, while the stop index is exclusive. This design choice is deliberate; it simplifies calculating the number of elements in the slice, which is simply stop minus start. Negative indexing allows you to access elements from the end of the list, with -1 referring to the last item. Because slices create new lists, they are inherently safe for manipulation; changing a slice does not affect the original list unless you perform an explicit slice assignment.

tasks = ["Email", "Docs", "Code", "Meeting", "Deploy"]

# Accessing elements via index
print(tasks[0])   # Outputs: Email

# Slicing the list (index 1 up to 4)
sub_tasks = tasks[1:4]  # ['Docs', 'Code', 'Meeting']

Modifying List Content

Lists are mutable, meaning their contents can be altered after creation without changing the object identity. You can update specific elements by assigning values to a given index, or use methods like 'append' to add an item to the end, or 'insert' to place an item at a specific position. It is important to grasp the difference between 'append', which adds one element as a single entry, and 'extend', which iterates over an iterable and adds each component to the list. When modifying a list, remember that insertion and deletion operations shift the existing elements in memory to maintain order. For large lists, frequent insertions at the beginning or middle can become computationally expensive, as the interpreter must shift subsequent references in the underlying array-like structure.

inventory = ["Keyboard", "Mouse"]

# Modifying a specific element
inventory[1] = "Wireless Mouse"

# Adding elements
inventory.append("Monitor")
inventory.extend(["Headset", "Webcam"])

Removing and Popping Elements

Removing elements from a list is a common task, and Python provides several methods depending on your requirements. The 'remove' method deletes the first occurrence of a specific value, while 'pop' removes an element at a specified index and returns that value to you. If you provide no index to 'pop', it defaults to the last element, making it ideal for stack-based workflows. Understanding how these methods work is vital because they modify the list in-place. If you attempt to remove a value that does not exist in the list, the interpreter will raise a ValueError, so it is often wise to check for presence using the 'in' operator beforehand. These operations, like modifications, trigger memory shifting, ensuring the list remains a compact, contiguous sequence of references.

logs = ["Info", "Error", "Debug", "Info"]

# Removing by value
logs.remove("Error")

# Removing by index
last_log = logs.pop()  # Removes the last element

List Comprehensions and Transformation

List comprehensions provide a concise and expressive syntax for creating new lists by applying operations to each item in an existing iterable. They are preferred over traditional 'for' loops for simple transformations because they are more readable and generally faster, as the list construction occurs at the C-language level within the interpreter. A comprehension follows the pattern of an expression followed by a 'for' clause and optional 'if' filters. When you use a list comprehension, you are essentially creating a new list object based on the filtering or mapping logic you define. This functional approach encourages cleaner code by avoiding the need for manual initialization and explicit 'append' calls, leading to fewer lines of code and reduced risk of side effects from external variable mutation during loop execution.

# Creating a list of squares using a comprehension
numbers = [1, 2, 3, 4, 5]
squared = [n**2 for n in numbers if n % 2 != 0]

# Result: [1, 9, 25]

Key points

  • Lists are ordered, mutable sequences that store collections of heterogeneous objects.
  • List indices start at zero, and negative indexing allows for easy access from the end.
  • Slicing creates a new list object containing the elements within the specified range.
  • Methods like append and extend modify the list in-place rather than returning a new instance.
  • The pop method is effective for retrieving and removing items, especially in stack-based patterns.
  • List comprehensions are an idiomatic and performant way to transform existing collections into new lists.
  • Because lists store references, assigning one variable to another creates a reference copy, not a deep copy.
  • Frequent modifications at the beginning or middle of large lists incur performance costs due to element shifting.

Common mistakes

  • Mistake: Using a mutable object like a list as a default argument. Why it's wrong: The list is created once when the function is defined, not when it is called, leading to shared state across calls. Fix: Use 'None' as the default value and initialize the list inside the function.
  • Mistake: Confusing .append() with .extend(). Why it's wrong: .append() adds an object as a single element, while .extend() unpacks an iterable and adds its elements individually. Fix: Use .append() for single items and .extend() for adding multiple items from another list.
  • Mistake: Modifying a list while iterating over it with a for loop. Why it's wrong: This causes the internal index pointer to skip elements or cause index out-of-range errors as the list size changes. Fix: Iterate over a copy of the list or use a list comprehension to create a new list.
  • Mistake: Expecting a list method like .sort() or .reverse() to return a new list. Why it's wrong: These methods modify the list in-place and return None. Fix: Use the built-in functions sorted() or reversed() if you need a new list returned, or assign the list to a variable before sorting.
  • Mistake: Thinking list slicing creates a deep copy. Why it's wrong: Slicing creates a shallow copy, meaning nested objects within the list are still referenced, not copied. Fix: Use the copy module's deepcopy function if you need a completely independent copy of a nested list.

Interview questions

What is the most basic way to create a list in Python, and how does it store elements?

The most basic way to create a list in Python is by using square brackets, such as 'my_list = [1, 2, 3]'. Lists are ordered, mutable collections that store elements in contiguous memory slots, which allows for efficient indexing. Because they are dynamic arrays, Python automatically handles the resizing of the list as you add or remove elements, making them a very flexible data structure for storing collections of items.

How can you add elements to a list, and what is the difference between append and extend?

To add elements, you can use the 'append()' method or the 'extend()' method. The 'append()' method adds a single element to the very end of the list, whereas 'extend()' takes an iterable and adds each of its elements to the list individually. For example, 'list.append([1, 2])' creates a nested list, while 'list.extend([1, 2])' adds the integers 1 and 2 directly to the list, expanding its total length.

Explain the process of list slicing and how it can be used to modify parts of a list.

List slicing allows you to extract a sub-section of a list using the syntax 'list[start:stop:step]'. It is powerful because it creates a shallow copy of that range. Beyond extraction, you can use slices to modify the list in-place. For example, 'my_list[1:3] = [10, 20]' replaces the elements at indices 1 and 2 with the new values. This is an efficient way to perform batch updates without manually iterating through indices.

Compare using list comprehension versus a standard for-loop to create a new list based on an existing one.

List comprehension provides a more concise, readable, and often faster syntax for creating a new list by applying an expression to each item in an existing iterable. While a standard for-loop with '.append()' is functionally correct, it requires multiple lines and explicit method calls. List comprehension is preferred in Python because it is optimized at the bytecode level, making it the idiomatic way to transform or filter data effectively.

How do you remove elements from a list, and what are the functional differences between 'remove', 'pop', and 'del'?

The 'remove()' method deletes the first occurrence of a specific value, throwing a ValueError if the item is missing. The 'pop()' method removes an element at a specific index and returns that value, defaulting to the last item. Finally, the 'del' statement is a keyword that removes an item at a specified index or deletes an entire slice without returning the value, which is useful for cleaning up large segments of data.

Discuss the time complexity of common list operations and how choosing the wrong method can impact performance in large datasets.

Python lists are implemented as dynamic arrays. Accessing or modifying by index is an O(1) operation. However, 'append()' is O(1) amortized, but inserting or deleting an item from the beginning of a list is O(n) because all subsequent elements must be shifted in memory. When working with massive datasets, repeatedly shifting elements in a list is highly inefficient; in such scenarios, using a deque from the collections module is often a better choice.

All Python interview questions →

Check yourself

1. What is the result of executing: x = [1, 2]; y = x; y.append(3); print(x)?

  • A.[1, 2]
  • B.[1, 2, 3]
  • C.[1, 2, 3, 3]
  • D.Error
Show answer

B. [1, 2, 3]
In Python, lists are mutable objects passed by reference. Assigning y = x makes y point to the same object as x. Modifying y also modifies x. Option 0 is wrong because the list was modified. Option 2 is wrong because 3 is only appended once. Option 3 is wrong because there is no error.

2. If list_a = [1, 2] and list_b = [3, 4], what does list_a.append(list_b) produce?

  • A.[1, 2, 3, 4]
  • B.[1, 2, [3, 4]]
  • C.[[1, 2], [3, 4]]
  • D.[3, 4]
Show answer

B. [1, 2, [3, 4]]
The .append() method adds its argument as a single element. Since list_b is a list, it is added as a nested list. Option 0 is the result of .extend(). Option 2 and 3 are incorrect because they change the structure of list_a in ways that .append() does not.

3. Given nums = [10, 20, 30, 40], what is the result of nums[1:3] = [5, 6, 7]?

  • A.[10, 5, 6, 7, 40]
  • B.[10, 5, 6, 30, 40]
  • C.[5, 6, 7, 40]
  • D.Error
Show answer

A. [10, 5, 6, 7, 40]
Slice assignment replaces the range defined by the slice (index 1 to 2) with the entirety of the assigned iterable. The indices 1 and 2 (values 20 and 30) are removed, and 5, 6, and 7 are inserted. Option 1 is wrong because it doesn't replace the full slice. Options 2 and 3 are incorrect manipulations of the slice.

4. Which of the following creates a new list containing elements 1, 2, and 3 without modifying the original?

  • A.my_list.sort()
  • B.my_list.append([1, 2, 3])
  • C.my_list + [1, 2, 3]
  • D.my_list.extend([1, 2, 3])
Show answer

C. my_list + [1, 2, 3]
The + operator for lists returns a new concatenated list. .sort(), .append(), and .extend() are all in-place methods that modify the original list and return None, making them unsuitable for creating a new list without side effects.

5. What is the output of: x = [1, 2]; print(x * 2)?

  • A.[1, 2, 1, 2]
  • B.[2, 4]
  • C.[1, 1, 2, 2]
  • D.Error
Show answer

A. [1, 2, 1, 2]
When used with a list, the * operator acts as a repetition operator, duplicating the elements of the list. Option 1 is mathematically incorrect for list operations. Option 2 implies an element-wise multiplication which Python lists do not support directly. Option 3 is a misconception of how repetition works.

Take the full Python quiz →

← PreviousClosuresNext →List Methods and Slicing

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app