Python Tutorial for Beginners #8: Introduction to Data Structures in Python

Raj PatelRaj Patel
15 min read

Imagine you are building a music streaming app like Spotify and you need to store:

  • A playlist of songs that user can add to, remove from and reorder.

  • User information like name, email and subscription type. It is more of the data that rarely changes.

  • Song ratings which are needed to find a user’s rating for any song. And this needs to be accessed quickly.

  • Unique Genres which might help to check whether a particular genre exists or not.

 # Without proper data structures the data is  messy and inefficient
playlist_song1 = "Laal Pari"
playlist_song2 = "Laila"
playlist_song3 = "Heer Aasmani"
user_name = "IronWill"
user_email = "ironwill@gmail.com"
user_subscription_type = "Premium"

#With proper data structures the data is clean and powerful
playlist = ["Laal Pari","Laila","Heer Aasmani"]
user_info = ["IronWIll","ironwill@gmail.com","Premium"]

See the difference? The right data structure transforms chaos into clarity. Moreover you can easily unpack your data or add or modify the existing data.

In today’s blog, we’ll dive into two of the most essential data structures in Python: lists and tuples. These are powerful tools that help you store, organize, and work with data efficiently. Through practical examples, you'll learn when to use a list, when a tuple is the better choice, and how each can make your code cleaner and more effective. In the next blog, we’ll continue by exploring dictionaries and sets. By mastering these core containers, you'll be well on your way to writing smarter and more robust Python programs. Let’s get started!


What are Data Structures?

Data structures are specialized formats for organizing, storing, and manipulating data in computer programs. Think of them as different types of containers, each designed with specific strengths and use cases. Just like you would not use a shopping bag to carry soup or a glass to transport books, different data structures are optimized for different tasks. In Python, we have several built-in data structures that are incredibly powerful and flexible. There are 4 types of data structures in Python, they are: Lists, Tuples, Dictionaries and Sets. Understanding the data structures is crucial because they determine how efficiently your program runs and they affect how easy your code is read and maintain. They influence how you approach problem solving and lay the foundation for more advanced programming concepts.

Lists

Lists are collection of items that can store items of any data type. They are probably the most versatile data type in Python. The list is represented by [] brackets and collection of items in enclosed in it. Lists are ordered and mutable (which means lists are changeable). You can create lists in multiple ways:

# Method 1: Empty List
shopping_list = []
numbers = list() # Alternate Way

# Method 2: List with initial values
fruits = ["apple", "banana", "orange", "grape"]
temperatures = [23.5, 25.0, 22.8, 24.1, 26.3]
mixed_data = ["John", 25, True, 5.8, "Engineer"]

# Method 3: Using list() constructor
digits = list("123456")
# digits = ['1','2','3','4','5','6']
range_lists = list(range(1,6))
# range_lists = [1,2,3,4,5]

# Method 4: List comprehension (We will discuss this in detail later)
squares = [x**2 for x in range(1,6)]
# squares = [1,4,9,16,25]

Lists use zero-based indexing, meaning the first element is at index 0. It also supports negative indexing which helps accessing the element from back(or in reversed order).

colors = ["red", "green", "blue", "yellow", "purple"]

# Positive indexing (from start)
print(f"First color: {colors[0]}")      # red
print(f"Second color: {colors[1]}")     # green
print(f"Third color: {colors[2]}")      # blue

# Negative indexing (from end)
print(f"Last color: {colors[-1]}")      # purple
print(f"Second last: {colors[-2]}")     # yellow

List Slicing

List Slicing allows you to extract portions of a list. It is exactly same as what we have seen with string slicing, if you have not seen that go and check out that first. The syntax is list[start:end:step]. By default the value of step is 1, however you can override it. Also you can negative indexes as well.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Basic slicing
print(f"First 5: {numbers[:5]}")        # [0, 1, 2, 3, 4]
print(f"Last 5: {numbers[-5:]}")        # [5, 6, 7, 8, 9]
print(f"Middle: {numbers[3:7]}")        # [3, 4, 5, 6]

# Step slicing
print(f"Every 2nd: {numbers[::2]}")     # [0, 2, 4, 6, 8]
print(f"Every 3rd from 1: {numbers[1::3]}")  # [1, 4, 7]
print(f"Reverse: {numbers[::-1]}")      # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# Slicing with variables
start, end = 2, 8
print(f"Dynamic slice: {numbers[start:end]}")  # [2, 3, 4, 5, 6, 7]

Adding elements to the lists

You can add items in the list with various built in functions like:

  1. append() - It is used to add single item in the list at the end.

     fruits = ["apple", "banana"]
     fruits.append("orange")
     print(f"After append: {fruits}")
     # Output: After append: ["apple", "banana", "orange"]
    
  2. insert(index,value) - It is used to add item at the specified position. In this you have to pass the index at which you want to insert at and also the value you want to insert.

     fruits = ["apple", "banana","orange"]
     fruits.insert(1,"kiwi")
     print(f"After insert: {fruits}")
     # Output:  After insert: ["apple", "kiwi", "banana", "orange"]
    
  3. extend() - It is used to add multiple items at the end.

     fruits = ["apple", "banana","orange"]
     fruits.extend(["grapes","strawberry"])
     print(f"After extend: {fruits}")
     # Output: After extend: ["apple", "banana", "orange","grapes","strawberry"]
    
  4. + operator - It is used to combine two lists and it always returns new list.

fruits = ["apple", "banana","orange"]
more_fruits = fruits + ["mango","guava"]
print(f"Using +: {more_fruits}")
# Output: Using +: ["apple","banana","orange","mango"."guava"]
  1. += operator - It is used to add the items at the end of the list and it does not return new list. It modifies the original list.
fruits = ["apple", "banana","orange"]
fruits += ["chickoo"]
print(f"Using +=: {fruits}")
# Output: Using +=: ["apple","banana","orange","chickoo"]

Difference between Append and Extend

append() is used to add a single item at the end of the list whereas extend is used to add multiple items at the end of list. If we have multiple elements in the element then they are appended as a single item only and creates a nested list. Let’s look at this example to make things very much clear

list1 = [1, 2, 3]
list2 = [1, 2, 3]

list1.append([4, 5])    # Adds the entire list as one element
list2.extend([4, 5])    # Adds each element individually

print(f"After append: {list1}")   # [1, 2, 3, [4, 5]]
print(f"After extend: {list2}")   # [1, 2, 3, 4, 5]

Removing elements from the List

You can remove items from the list with various built in functions like:

  1. remove(item) - This function when passed with the item to be removed, removes that particular item from the list. Its syntax is list.remove(item)

     fruits = ["apple","banana","strawberry","mango"]
     fruits.remove("strawberry")
     print(fruits)
     # Output: ["apple","banana","mango"]
    

    In case there are multiple items of the same name, then this function removes only the first occurrence of that item. The later elements are not removed.

     fruits = ["apple","banana","strawberry","mango","banana"]
     fruits.remove("banana")
     print(fruits)
     # Output: ["apple","strawberry","mango","banana"]
    
  2. pop() - This function removes and returns the element from the list at the index passed as a parameter to this function. By default it removes and returns the last element.

     fruits = ["apple","banana","strawberry","mango","banana"]
     last_fruit = fruits.pop()  #By default would remove the last element entered in the list
     print(f"Popped: {last_fruits}, and Remaining: {fruits}")
     # Output: Popped: banana, and Remaining: ["apple","banana","strawberry","mango"]
    
     third_fruit = fruits.pop(2)
     print(f"Popped: {third_fruit}, and Remaining: {fruits}")
     # Output: Popped: strawberry, and Remaining: ["apple","banana","mango"]
    
  3. del statement - This removes the element by index or by slicing.

     fruits = ["apple","banana","strawberry","mango","banana"]
     del fruits[2]
     print(f"After del: {fruits}")
     # Output: ["apple","banana","mango","banana"]
    

    If you want to delete by slicing, the you need to mention start, stop and step. Its syntax is:

    del[start:stop:step]. By default the value of step is 1, start is 0, and stop is n(where n is the number of elements).

     fruits = ["apple","banana","strawberry","mango","banana"]
     del fruits[2:5]
     print(f"After del: {fruits}")
     # Output: ["apple","banana"]
    
  4. clear() - This method removes all the elements from the list. Its syntax is list_name.clear()

     fruits = ["apple","banana","strawberry","mango","banana"]
     fruits.clear()
     print(f"After clearing: {fruits}")
     # Output: []
    

Modifying Lists

  1. Changing Elements - You can change the elements inside a list by overriding them. You can even change multiple elements with slicing. For changing a single element the syntax is listname[index]. Remember list has zero based indexing means the first element will be at 0th index.

     numbers = [1,2,3,4,5,6]
     numbers[2] = 20 # This would change the 3rd element in the list that is 3 to 20.
     print(numbers)
     # Output: [1,2,20,4,5,6]
    
     #For changing multiple items use slicing
     numbers = [1,2,3,4,5,6]
     numbers[3:5] = [30,40]  # This would change the 4th and 5th element in the list to 30 and 40
     print(numbers)
     # Output: [1,2,3,30,40,6]
    

    In slicing it gets very easy to change only some elements with the help of step, let’s say you want to add the new elements at the odd places in the list:

     numbers = [1,2,3,4,5,6]
     numbers[::2] = [10,20,30]
     print(numbers)
     # Output: [10, 2, 20, 4, 30, 6]
    

    BrainTeaser

    What happens if I try replacing with different number of elements. Look at the below example:

     numbers = [1,2,3,4,5,6]
     numbers[1:3] = [10,20,30,40]
     print(numbers)
    

    What will be the output here? Will the list only replace 2nd and 3rd element ? Or will the last 2 elements also get replaced? Think before you look at the answer below or run the code directly as this will really help you boost your thinking skills.

    Output:

     [1,10,20,30,40,4,5,6]
    

    Here in this case the size of the list gets increased and it accommodates all the elements. The original elements from 1st index and 2nd index were replaced with 10 and 20 respectively. The other two elements(3,4) were shifted right to accommodate 30 and 40, thus making the list size to 7.

  2. reverse - This method reverses the original list. Its syntax is list_name.reverse()

     original = [1, 2, 3, 4, 5]
     original.reverse()  
     print(f"Reversed: {original}")
     # Output: [5,4,3,2,1]
    
  3. sort() - This method is used to arrange the elements in a list in particular order. By default this order is ascending.

     scores = [78, 92, 85, 91, 88]
     scores.sort()  # Ascending order
     print(f"Sorted ascending: {scores}")
     # Output: [78,85,88,91,92]
    

    However if we want to sort the items in descending order then we got to pass reverse=True as argument to sort.

     scores = [78, 92, 85, 91, 88]
     scores.sort(reverse=True)  # Descending order
     print(f"Sorted descending: {scores}")
     # Output: [92,91,88,85,78]
    

Essential Methods

  1. count() - This method would return the occurrence of a particular element in a list. Its syntax is list_name.count(element).

     numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
     count_of_1 = numbers.count(1)
     count_of_5 = numbers.count(5)
     print(f"1 appears {count_of_1} times, 5 appears {count_of_5} times")
     # Output: 1 appears 2 times, 5 appears 2 times
    
  2. index() - This method would return the index of first occurence of a particular element in a list. Its syntax is list_name.index(element).

     numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
     index_of_4 = numbers.index(4)
     print(f"First 4 is at index: {index_of_4}")
     # Output: First 4 is at index: 2
    

    If you want to start searching after a particular index then simply specify the index after which you would like to search and get the index. In this case this syntax would be list_name.index(value,starting_index). If it is not found then it would raise a ValueError.

     numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
     index_of_5_after_3 = numbers.index(5, 3)  # Search from index 3
     print(f"First 5 after index 3: {index_of_5_after_3}")
     # Output: First 5 after index 3: 4
    
  3. copy() - This method creates a shallow copy of the list. A shallow copy means that the outer list is copied, but any nested (inner) objects, such as other lists, are not duplicated. Instead, the references to those inner objects are copied. In other words, the top-level list is a new object in memory, but nested items (like lists inside the list) still point to the same memory locations as in the original list.

     original = [1, 2, [3, 4], 5]
     shallow_copy = original.copy()
     reference = original
    
     # Modify original list item
     original[0] = 100
     original[2][0] = 300  # Modify nested list. This would lead to modification in both original list
                                                                              # and shallow list.
    
     print(f"Original: {original}")  # Output:[100, 2, [300, 4], 5]
     print(f"Shallow copy: {shallow_copy}")  # Output:[1, 2, [300, 4], 5]
     print(f"Reference: {reference}")   #  Output:[100, 2, [300, 4], 5]
    
  4. sorted() - This method returns the new sorted list. It does not modify original list. By default it sorts in ascending order.

     numbers = [3, 1, 4, 1, 5, 9, 2]
     sorted_numbers = sorted(numbers)
     print(f"Original: {numbers}")
     print(f"Sorted copy: {sorted_numbers}")
     # Output:
     # Original: [3, 1, 4, 1, 5, 9, 2]
     # Sorted copy: [1,1,2,3,4,5,9]
    

    If you want to perform custom sorting like you want to sort by length, then you can pass key=len in the argument alongside the listname. In case of sorting in descending order then pass reverse=True as an argument alongside the listname.

     words = ["apple", "pie", "cricket", "book", "python"]
     sorted_by_length = sorted(words, key=len)
     sorted_reverse = sorted(words, reverse=True)
     print(f"Sorted by length: {sorted_by_length}")
     print(f"Reverse alphabetical: {sorted_reverse}")
     # Output:
     # Sorted by length: ["pie","book","apple","python","cricket"]
     # Reverse alphabetical : ["python","pie","cricket","book","apple"]
    

Tuples

Tuples are collection of items that can store items of any data type. A tuple is represented by () brackets and collection of items enclosed in it. The difference between lists and tuples is that tuples cannot be changed after creation which means they are perfect for storing related data that is expected to remain constant. You can create tuple in many ways:

# Method 1: Empty Tuple
shopping = ()
market_basket = tuple(
) # Alternate Way

# Method 2: Tuple with initial values
coordinates = (10.5,11.5,22,13)
colors = ("red", "green", "blue")
mixed_tuple = ("Rahul", 25, True, 3.14159)

#Method 3: Using tuple() constructor
name = ("Rahul","Rohit","Hardik","Virat")

For single item tuple after passing the item putting , is crucial as then and then only it would be considered as tuple. Otherwise it would be considered just as the normal data type of string or integer in parantheses.

single_item = ("hello",)  # Without comma, it's just a string in parentheses
not_a_tuple = ("hello")   # This is just a string

print(f"Single item tuple: {type(single_item)}")  # <class 'tuple'>
print(f"Not a tuple: {type(not_a_tuple)}")        # <class 'str'>

You can create tuple without parentheses as well. This is called tuple packing and this works only if there are more than one items.

point = 5, 10, 15
print(f"Point: {point}, Type: {type(point)}")
# Output: Point: (5, 10, 15), Type: <class 'tuple'>

You can convert other sequences to tuples as well.

list_to_tuple = tuple([1, 2, 3, 4])
string_to_tuple = tuple("hello")
range_to_tuple = tuple(range(5))

print(f"From list: {list_to_tuple}")
#Output: From list: (1,2,3,4,5)
print(f"From string: {string_to_tuple}")
#Output: From string: ('h','e','l','l','o')
print(f"From range: {range_to_tuple}")
#Output: From range: (0,1,2,3,4)

Tuple Slicing

Tuple Slicing allows you to extract portions of a tuple. It is exactly same as what we have seen with string slicing, if you have not seen that go and check out that first. The syntax is tuple[start:end:step]. By default the value of step is 1, however you can override it. Also you can negative indexes as well.

numbers = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
# Basic slicing
print(f"First 5: {numbers[:5]}")        # (0, 1, 2, 3, 4)
print(f"Last 5: {numbers[-5:]}")        # (5, 6, 7, 8, 9)
print(f"Middle: {numbers[3:7]}")        # (3, 4, 5, 6)

# Step slicing
print(f"Every 2nd: {numbers[::2]}")     # (0, 2, 4, 6, 8)
print(f"Every 3rd from 1: {numbers[1::3]}")  # (1, 4, 7)
print(f"Reverse: {numbers[::-1]}")      # (9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

# Slicing with variables
start, end = 2, 8
print(f"Dynamic slice: {numbers[start:end]}")  # (2, 3, 4, 5, 6, 7)

Tuples do not allow adding elements, removing elements or modifying elements like lists as tuples cannot be changed after creation. However, for tuples you can use some methods like:

  1. count() - This method would return the occurrence of a particular element in a tuple. Its syntax is tuple_name.count(element).

     numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)
     count_of_1 = numbers.count(1)
     count_of_5 = numbers.count(5)
     print(f"1 appears {count_of_1} times, 5 appears {count_of_5} times")
     # Output: 1 appears 2 times, 5 appears 2 times
    
  2. index() - This method would return the index of first occurence of a particular element in a tuple. Its syntax is tuple_name.index(element).

     numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)
     index_of_4 = numbers.index(4)
     print(f"First 4 is at index: {index_of_4}")
     # Output: First 4 is at index: 2
    

    If you want to start searching after a particular index then simply specify the index after which you would like to search and get the index. In this case this syntax would be tuple_name.index(value,starting_index). If it is not found then it would raise a ValueError.

     numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)
     index_of_5_after_3 = numbers.index(5, 3)  # Search from index 3
     print(f"First 5 after index 3: {index_of_5_after_3}")
     # Output: First 5 after index 3: 4
    
  3. Tuple concatenation - When two tuples are concatenated, it produces a new tuple. Concatenation of tuples are concatenated is performed using + operator.

     tuple1 = (1, 2, 3)
     tuple2 = (4, 5, 6)
     combined = tuple1 + tuple2
     repeated = tuple1 * 3 # This would create a new tuple with repeating elements of tuple1 thrice
    
     print(f"Combined: {combined}")
     # Combined: (1, 2, 3, 4, 5, 6)
    
     print(f"Repeated: {repeated}")
     # Repeated: (1, 2, 3, 1, 2, 3, 1, 2, 3)
    
  4. _replace() - This function would create a new tuple and modify that

In tuple there is no copy function , we simply assign the reference variable of a tuple to another reference variable. Since tuples cannot be changed, there is generally no risk in multiple variables referencing the same tuple. Python optimizes memory usage by safely allowing shared references to immutable objects. However, this is still referencing the same tuple under the hood.


When to use Tuples vs Lists

You can use tuples for values that do not need modification such as coordinates, RGB values for particular colours or database records.

# 1. Coordinates that should not change
ORIGIN = (0, 0)
SCREEN_SIZE = (1920, 1080)

# 2. RGB values for colours
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# 3. Database records
student_record = ("S001", "KL Rahul", "Computer Science", 3.8)

You can use lists for values that can have alterations like shopping carts or data that needs sorting. Basically anything that modifies the original content.

# Use lists for:

# 1. Collections that change
shopping_cart = ["milk", "bread", "eggs"]
shopping_cart.append("cheese")  # This is expected to change

# 2. Data that needs sorting
scores = [85, 92, 78, 91, 88]
scores.sort()  # We want to modify the original

Practical example of lists and tuples

class Inventory:
    def __init__(self):
        self_products = []  # List of product tuples

    def add_product(self, name, product_id, price):
        product = (name, product_id, price)  # Tuple for each product
        self_products.append(product)

    def get_total_value(self):
        return sum(price for name, product_id, price in self_products)

# Usage
inv = Inventory()
inv.add_product("Laptop", "P1001", 1200)
inv.add_product("Mouse", "P1002", 25)

print("Total Inventory Value:", inv.get_total_value())

Lists and tuples may seem similar at first glance, but each serves its own purpose in Python programming. Lists are flexible and ideal for situations where your data needs to change—add, remove, or update elements. Tuples, on the other hand, offer simplicity and performance when your data is fixed and should remain unchanged.

By understanding the strengths and differences of these two structures, you’ve taken an important step toward writing more efficient, readable, and Pythonic code. In the next blog, we’ll build on this foundation by exploring dictionaries and sets—two more tools that will further expand your ability to manage data effectively.

Until then, keep experimenting and practicing with what you’ve learned today. Happy coding!

0
Subscribe to my newsletter

Read articles from Raj Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Raj Patel
Raj Patel

Hey there! I'm Raj Patel, a passionate learner and tech enthusiast currently diving deep into Data Science, AI, and Web Development. I enjoy turning complex problems into simple, intuitive solutions—whether it's building real-world ML projects like crop disease detection for farmers or creating efficient web platforms for everyday use. I’m currently sharpening my skills in Python, Machine Learning , Javascript and love sharing what I learn through blogs and open-source contributions. If you're into AI, clean code, or building impactful tech—let’s connect!