List Data Structure in Python

What is a List?

So far you've stored one value in one variable:


    student1 = "Rahul"
    student2 = "Priya"
    student3 = "Gagan"

What if you have 100 students? You can't create 100 variables. That's where Lists come in.

A list stores multiple values in a single variable:


    students = ["Rahul", "Priya", "Gagan"]

Done. 3 students in one variable. Scale this to 100 or 1000 — same concept.


Creating a List


    # List of strings
    fruits = ["apple", "banana", "mango", "orange"]

    # List of numbers
    marks = [85, 90, 78, 92, 88]

    # List of mixed types (allowed in Python)
    mixed = ["Gagan", 22, 5.11, True]

    # Empty list
    empty = []

    print(fruits)
    print(marks)

Output:

['apple', 'banana', 'mango', 'orange']
[85, 90, 78, 92, 88]

Indexing — Accessing Items

Every item in a list has a position called index. Indexing starts from 0 in Python:


    fruits = ["apple", "banana", "mango", "orange"]
    #           0         1         2        3

    print(fruits[0])    # apple
    print(fruits[1])    # banana
    print(fruits[2])    # mango
    print(fruits[3])    # orange


Negative Indexing

Python also allows counting from the end using negative numbers:


    fruits = ["apple", "banana", "mango", "orange"]

    print(fruits[-1])    # orange  (last item)
    print(fruits[-2])    # mango   (second last)
    print(fruits[-4])    # apple   (fourth from end)

Very useful when you want the last item and don't know the length of the list.


Slicing — Getting a Portion of a List

You can extract a section of a list using [start:end]:


    fruits = ["apple", "banana", "mango", "orange", "grapes"]

    print(fruits[1:3])     # ['banana', 'mango']  (index 1 and 2, not 3)
    print(fruits[0:3])     # ['apple', 'banana', 'mango']
    print(fruits[2:])      # ['mango', 'orange', 'grapes']  (from index 2 to end)
    print(fruits[:3])      # ['apple', 'banana', 'mango']   (from start to index 2)
    print(fruits[:])       # entire list

Same rule as range — end index is not included.


Modifying Items

Lists are mutable — you can change their values after creation:


    fruits = ["apple", "banana", "mango"]
    print(fruits)          # ['apple', 'banana', 'mango']

    fruits[1] = "grapes"   # replace banana with grapes
    print(fruits)          # ['apple', 'grapes', 'mango']


List Methods — Built-in Operations

Python gives you many built-in methods to work with lists:

Adding Items


    fruits = ["apple", "banana"]

    fruits.append("mango")         # add to the END
    print(fruits)                  # ['apple', 'banana', 'mango']

    fruits.insert(1, "orange")     # add at specific index
    print(fruits)                  # ['apple', 'orange', 'banana', 'mango']


Removing Items


    fruits = ["apple", "banana", "mango", "orange"]

    fruits.remove("banana")        # remove by value
    print(fruits)                  # ['apple', 'mango', 'orange']

    fruits.pop()                   # remove last item
    print(fruits)                  # ['apple', 'mango']

    fruits.pop(0)                  # remove by index
    print(fruits)                  # ['mango']


Other Useful Methods


    numbers = [3, 1, 4, 1, 5, 9, 2, 6]

    print(len(numbers))            # 8 — total items in list
    print(numbers.count(1))        # 2 — how many times 1 appears
    print(numbers.index(5))        # 4 — index of value 5

    numbers.sort()                 # sort in ascending order
    print(numbers)                 # [1, 1, 2, 3, 4, 5, 6, 9]

    numbers.reverse()              # reverse the list
    print(numbers)                 # [9, 6, 5, 4, 3, 2, 1, 1]

    numbers.sort(reverse=True)     # sort in descending order
    print(numbers)                 # [9, 6, 5, 4, 3, 2, 1, 1]


Checking if Item Exists


    fruits = ["apple", "banana", "mango"]

    print("banana" in fruits)      # True
    print("grapes" in fruits)      # False

    if "mango" in fruits:
        print("Yes we have mango!")


Looping Through a List

This is where lists really shine — combine with for loops:


    fruits = ["apple", "banana", "mango", "orange"]

    for fruit in fruits:
        print(fruit)

Output:

apple
banana
mango
orange

With index using enumerate():


    fruits = ["apple", "banana", "mango"]

    for index, fruit in enumerate(fruits):
        print(f"{index + 1}. {fruit}")

Output:

1. apple
2. banana
3. mango

enumerate() gives you both the index and the value at the same time. Very useful.


List with Functions — Practical Example


    def get_total(marks):
        total = 0
        for mark in marks:
            total += mark
        return total

    def get_average(marks):
        return get_total(marks) / len(marks)

    def get_highest(marks):
        return max(marks)

    def get_lowest(marks):
        return min(marks)


    marks = [85, 90, 78, 92, 88, 76]

    print(f"Marks: {marks}")
    print(f"Total: {get_total(marks)}")
    print(f"Average: {get_average(marks):.2f}")
    print(f"Highest: {get_highest(marks)}")
    print(f"Lowest: {get_lowest(marks)}")

Output:

Marks: [85, 90, 78, 92, 88, 76]
Total: 509
Average: 84.83
Highest: 92
Lowest: 76

List of Lists — 2D List

A list can contain other lists. This is like a table with rows and columns:


    students = [
        ["Rahul", 85, "A"],
        ["Priya", 92, "A+"],
        ["Gagan", 78, "B"]
    ]

    # Accessing
    print(students[0])          # ['Rahul', 85, 'A']
    print(students[0][0])       # Rahul  (row 0, column 0)
    print(students[1][1])       # 92     (row 1, column 1)

    # Looping
    for student in students:
        print(f"Name: {student[0]}, Marks: {student[1]}, Grade: {student[2]}")

Output:

Name: Rahul, Marks: 85, Grade: A
Name: Priya, Marks: 92, Grade: A+
Name: Gagan, Marks: 78, Grade: B

Common Mistake — Copying a List

This is a classic beginner trap:


    list1 = [1, 2, 3]
    list2 = list1          # this is NOT a copy — both point to same list

    list2.append(4)
    print(list1)           # [1, 2, 3, 4] — list1 also changed!
    print(list2)           # [1, 2, 3, 4]

To make a proper independent copy:


    list1 = [1, 2, 3]
    list2 = list1.copy()   # proper copy

    list2.append(4)
    print(list1)           # [1, 2, 3]   — unchanged
    print(list2)           # [1, 2, 3, 4]


Real World Example — Shopping Cart


    cart = []

    def show_cart():
        if len(cart) == 0:
            print("Cart is empty")
        else:
            print("\n=== Your Cart ===")
            for index, item in enumerate(cart):
                print(f"{index + 1}. {item}")
            print(f"Total items: {len(cart)}")

    def add_item():
        item = input("Enter item name: ")
        cart.append(item)
        print(f"{item} added to cart!")

    def remove_item():
        show_cart()
        if len(cart) > 0:
            item = input("Enter item to remove: ")
            if item in cart:
                cart.remove(item)
                print(f"{item} removed!")
            else:
                print("Item not found in cart")

    while True:
        print("\n=== Shopping Cart ===")
        print("1. Add item")
        print("2. Remove item")
        print("3. View cart")
        print("4. Exit")

        choice = input("Enter choice: ")

        if choice == "1":
            add_item()
        elif choice == "2":
            remove_item()
        elif choice == "3":
            show_cart()
        elif choice == "4":
            print("Thank you for shopping!")
            break
        else:
            print("Invalid choice")

This is a real mini-application — using everything you've learned so far together.


Quick Reference — All List Methods

fruits = ["apple", "banana", "mango"]

fruits.append("orange")       # add to end
fruits.insert(1, "grapes")    # add at index
fruits.remove("banana")       # remove by value
fruits.pop()                  # remove last
fruits.pop(0)                 # remove by index
fruits.sort()                 # sort ascending
fruits.reverse()              # reverse
fruits.clear()                # remove all items
fruits.copy()                 # make a copy
len(fruits)                   # count items
"apple" in fruits             # check existence
fruits.index("mango")         # find index of value
fruits.count("apple")         # count occurrences

Exercise 🏋️

Build a Number Analyzer program:

  1. Ask user to enter 5 numbers one by one and store them in a list
  2. Then display:
    • All numbers
    • Sum of all numbers
    • Average
    • Highest number
    • Lowest number
    • All even numbers from the list
    • All odd numbers from the list
    • List sorted in ascending order

Expected output:

Enter number 1: 15
Enter number 2: 8
Enter number 3: 23
Enter number 4: 4
Enter number 5: 16

=== Analysis ===
Numbers: [15, 8, 23, 4, 16]
Sum: 66
Average: 13.20
Highest: 23
Lowest: 4
Even numbers: [8, 4, 16]
Odd numbers: [15, 23]
Sorted: [4, 8, 15, 16, 23]

Hint: Use a for loop to collect numbers, and another for loop to separate even and odd numbers into two separate lists.


Functions in Python

What is a Function?

The Problem Without Functions

Look at this program:


    # Calculating area of rectangle 1
    length1 = 10
    width1 = 5
    area1 = length1 * width1
    print(f"Area of rectangle 1: {area1}")

    # Calculating area of rectangle 2
    length2 = 8
    width2 = 3
    area2 = length2 * width2
    print(f"Area of rectangle 2: {area2}")

    # Calculating area of rectangle 3
    length3 = 15
    width3 = 7
    area3 = length3 * width3
    print(f"Area of rectangle 3: {area3}")

You're writing the same logic 3 times. Now imagine doing this for 100 rectangles. Nightmare.

Functions solve this problem.


What is a Function?

A function is a reusable block of code that you write once and use as many times as you want.

Real life analogy: Think of a function like a mixer/blender. You built it once. Now whenever you want to blend something — you just put ingredients in and press the button. You don't rebuild the blender every time.


Basic Syntax


    def function_name():
        # code inside function

  • def keyword tells Python "I'm defining a function"
  • function_name is what you name it
  • : colon at the end — just like if/for/while
  • Code inside must be indented

Your First Function


    def say_hello():
        print("Hello!")
        print("Welcome to Python")

    # Calling the function
    say_hello()

Output:
Hello!
Welcome to Python

Defining a function doesn't run it. You have to call it by writing its name with ().


Call it Multiple Times


    def say_hello():
        print("Hello!")
        print("Welcome to Python")

    say_hello()    # first call
    say_hello()    # second call
    say_hello()    # third call

Output:

Hello!
Welcome to Python
Hello!
Welcome to Python
Hello!
Welcome to Python

Write once, use many times. That's the power.


Parameters — Giving Input to Functions

Right now our function does the same thing every time. What if we want it to greet different people?

We use parameters — variables that receive values when the function is called:


    def greet(name):
        print(f"Hello, {name}!")
        print(f"Welcome to Python, {name}!")

    greet("Gagan")
    greet("Rahul")
    greet("Priya")

Output:

Hello, Gagan!
Welcome to Python, Gagan!
Hello, Rahul!
Welcome to Python, Rahul!
Hello, Priya!
Welcome to Python, Priya!

Here name is a parameter — it acts like a variable inside the function that receives whatever value you pass when calling.


Multiple Parameters


    def greet(name, city):
        print(f"Hello {name}, from {city}!")

    greet("Gagan", "Delhi")
    greet("Rahul", "Mumbai")

Output:

Hello Gagan, from Delhi!
Hello Rahul, from Mumbai!

Order matters — first value goes to first parameter, second to second.


Now Fix the Rectangle Problem


    def calculate_area(length, width):
        area = length * width
        print(f"Area = {area}")

    calculate_area(10, 5)
    calculate_area(8, 3)
    calculate_area(15, 7)

Output:

Area = 50
Area = 24
Area = 105

Same logic written once, used 3 times. Clean and simple.


Return — Getting Output from a Function

So far our functions just print things. But what if you want to use the result of a function somewhere else in your code?

Use the return keyword:


    def calculate_area(length, width):
        area = length * width
        return area          # send this value back to whoever called the function

Now you can store the returned value:


    def calculate_area(length, width):
        area = length * width
        return area

    result = calculate_area(10, 5)
    print(f"The area is {result}")

    # Or use it directly in math
    total_area = calculate_area(10, 5) + calculate_area(8, 3)
    print(f"Total area = {total_area}")

Output:

The area is 50
Total area = 74

print vs return — Important Difference

This confuses many beginners. Let's clear it up:


    # Function with print — just displays, can't reuse the value
    def add_with_print(a, b):
        print(a + b)

    # Function with return — sends value back, you can use it
    def add_with_return(a, b):
        return a + b
    result1 = add_with_print(3, 4)    # prints 7, but result1 is None
    result2 = add_with_return(3, 4)   # result2 is 7, you can use it

    print(result1)          # None
    print(result2)          # 7
    print(result2 * 2)      # 14 — you can do math with it

Rule of thumb:

  • Use print inside a function only for debugging or when displaying is the only purpose
  • Use return when you need to use the result somewhere else — which is most of the time

Default Parameters

You can give parameters a default value — used when caller doesn't provide one:


    def greet(name, message="Good morning"):
        print(f"{message}, {name}!")

    greet("Gagan")                        # uses default message
    greet("Rahul", "Good evening")        # overrides default
    greet("Priya", "Happy birthday")      # overrides default

Output:
Good morning, Gagan!
Good evening, Rahul!
Happy birthday, Priya!

Parameters with defaults must always come after parameters without defaults:

def greet(message="Hello", name):   # WRONG
def greet(name, message="Hello"):   # CORRECT

Returning Multiple Values

Python functions can return more than one value:


    def min_max(a, b, c):
        minimum = min(a, b, c)
        maximum = max(a, b, c)
        return minimum, maximum

    small, large = min_max(5, 2, 8)
    print(f"Min: {small}, Max: {large}")

Output:

Min: 2, Max: 8

min() and max() are Python built-in functions that find smallest and largest values.


Scope — Where Variables Live

This is important to understand:

Local variable — created inside a function, only exists inside that function:


    def my_function():
        x = 10          # local variable — only exists inside this function
        print(x)

    my_function()
    print(x)            # ERROR! x doesn't exist outside the function

Global variable — created outside all functions, accessible everywhere:


    name = "Gagan"      # global variable

    def my_function():
        print(name)     # can access global variable inside function

    my_function()       # Gagan
    print(name)         # Gagan — works outside too

Think of it like this — what happens in a function, stays in a function. Local variables are created when the function runs and destroyed when it finishes.


Real World Example — Calculator with Functions

Let's rebuild the calculator properly using functions:


    def add(a, b):
        return a + b

    def subtract(a, b):
        return a - b

    def multiply(a, b):
        return a * b

    def divide(a, b):
        if b == 0:
            return "Error: Cannot divide by zero"
        return a / b

    def show_menu():
        print("\n=== Calculator ===")
        print("1. Add")
        print("2. Subtract")
        print("3. Multiply")
        print("4. Divide")
        print("5. Exit")

    while True:
        show_menu()
        choice = input("Enter choice: ")

        if choice == "5":
            print("Goodbye!")
            break

        num1 = float(input("Enter first number: "))
        num2 = float(input("Enter second number: "))

        if choice == "1":
            print(f"Result: {add(num1, num2)}")
        elif choice == "2":
            print(f"Result: {subtract(num1, num2)}")
        elif choice == "3":
            print(f"Result: {multiply(num1, num2)}")
        elif choice == "4":
            print(f"Result: {divide(num1, num2)}")
        else:
            print("Invalid choice")

Notice how clean this is — each operation has its own function. If you want to change how addition works, you only change it in one place.


Why Functions Matter — Summary

Without functions:

  • Code is repeated everywhere
  • Hard to fix bugs — you have to fix the same thing in 10 places
  • Hard to read and understand

With functions:

  • Write once, use anywhere
  • Fix in one place — fixed everywhere
  • Code is organized and readable

This principle is called DRY — Don't Repeat Yourself. It's one of the most important principles in programming.


Exercise 🏋️

Build a Student Grade Calculator using functions:

Create these functions:

  1. get_average(marks) — takes a list of 5 subject marks as separate parameters and returns the average
  2. get_grade(average) — takes average and returns grade (A/B/C/D/F using same logic as before)
  3. print_report(name, average, grade) — prints a formatted report card

Then write a main program that:

  • Asks for student name
  • Asks for marks in 5 subjects
  • Calls all three functions and displays the report

Expected output:

=== Report Card ===
Student: Gagan
Marks: 85, 90, 78, 92, 88
Average: 86.6
Grade: B
===================

Phase 3 — Components Deep Dive

Chapter 1 — What We Are Going to Learn and Why In Phase 2 you learned what a component is and how to create one. You know that a component h...