Logo OnlineCBT
PYTHON Tutorial
PYTHON · LESSON 21

Lists: Create, Update, Slice and Copy

Use Python's mutable ordered collection safely, choose efficient list operations, and avoid accidental shared-reference bugs.

Level: Beginner to Advanced
Duration: ~60 Mins Deep-Dive
Updated: 26 Jul 2026

Lesson 21: Lists - Create, Update, Slice and Copy

1. Introduction to Advanced Data Structures in Python

Welcome to Module 2 of our Python Mastery Series! In Module 1, we established a rock-solid foundation in Python syntax, primitive data types (integers, floats, strings, Booleans), control flow (if-elif-else, match-case), loops (for, while), modular functions, scope, and environment management.

Now, we transition into Data Structures in Depth. Real-world applications—whether managing financial ledgers, tracking inventory items, processing HTTP payloads, or analyzing data science collections—depend on storing and organizing multiple data values inside unified, flexible containers.

The most foundational, versatile, and frequently used sequence structure in Python is the List (`list`). In this lesson, we will explore creating lists, dynamic mutation methods, advanced slicing, memory pointer references, and the critical distinction between Shallow Copying and Deep Copying.


2. Deep Dive: What is a Python List?

1. Core Characteristics of the `list` Type

In Python, a list is an instance of the built-in list class. It possesses four fundamental architectural properties:

  • Ordered: Elements maintain a strict sequential index order from index 0 to len(list) - 1.
  • Mutable: Items can be added, updated, replaced, or removed in-place without altering the underlying memory object ID.
  • Heterogeneous: A single list can store mixed data types simultaneously (integers, strings, floats, Booleans, lists, dictionaries).
  • Dynamic Resizing: Python automatically manages memory allocation, growing or shrinking list capacity as elements are inserted or deleted.
In-Memory Representation: Python lists do NOT store raw primitive values directly in contiguous memory blocks. Instead, a Python list is an array of object references (pointers) pointing to memory locations on the Python Heap where actual data objects reside.

2. Creating Lists in Python

Lists can be created using square brackets [] or the built-in list() constructor function:

LIST_CREATION_DEMO.PY
# 1. Empty List Creation
empty_list_1 = []
empty_list_2 = list()

# 2. Homogeneous Primitive List
integers = [10, 20, 30, 40, 50]

# 3. Heterogeneous Mixed Data Type List
mixed_profile = ["alice_dev", 101, 95.5, True, ["Admin", "Developer"]]

# 4. Converting Iterables using list() constructor
char_list = list("Python")
range_list = list(range(1, 6))

print("Mixed Profile Array :", mixed_profile)
print("String to Char List :", char_list)
print("Range Generator List:", range_list)
print("Object Memory ID    :", id(mixed_profile))
OUTPUT
Mixed Profile Array : ['alice_dev', 101, 95.5, True, ['Admin', 'Developer']]
String to Char List : ['P', 'y', 't', 'h', 'o', 'n']
Range Generator List: [1, 2, 3, 4, 5]
Object Memory ID    : 140239482910224

3. Updating and Mutating Lists In-Place

Because lists are mutable, Python provides a rich set of built-in methods to modify list contents dynamically.

1. Inserting Elements (`.append()`, `.extend()`, `.insert()`)

  • .append(item): Adds a single object to the end of the list. Time Complexity: $O(1)$ amortized.
  • .extend(iterable): Unpacks an iterable sequence and appends every element to the end of the list.
  • .insert(index, item): Inserts a single object at a specific zero-based index, shifting existing items to the right. Time Complexity: $O(n)$.
LIST_MUTATION_INSERTION.PY
inventory = ["Laptop", "Mouse"]

# 1. Appending single items
inventory.append("Keyboard")
print("After append('Keyboard') :", inventory)

# 2. Extending with another iterable sequence
new_shipment = ["Monitor", "Headphones"]
inventory.extend(new_shipment)
print("After extend(new_shipment):", inventory)

# 3. Inserting at specific index (0 = front of list)
inventory.insert(0, "Webcam")
print("After insert(0, 'Webcam') :", inventory)
OUTPUT
After append('Keyboard') : ['Laptop', 'Mouse', 'Keyboard']
After extend(new_shipment): ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Headphones']
After insert(0, 'Webcam') : ['Webcam', 'Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Headphones']

2. Deleting Elements (`.pop()`, `.remove()`, `del`, `.clear()`)

  • .pop([index])
  • Optional index (defaults to -1, last item)
  • Removes element at index and returns it. Raises IndexError if empty.
  • Returns popped item
  • .remove(value)
  • Target value to match
  • Searches left-to-right and removes the **first occurrence** of value. Raises ValueError if missing.
  • Returns None
  • del list[index]
  • Index or slice range
  • Python keyword that deletes item or slice in-place without method overhead.
  • No return value
  • .clear()
  • No parameters
  • Removes all items, resetting list length to 0 while preserving object ID.
  • Returns None
Deletion Method Parameter Syntax Behavior Description Return Value
LIST_MUTATION_DELETION.PY
tasks = ["Code", "Test", "Review", "Deploy", "Test"]

# 1. pop() removes and returns item by index (or last item)
last_task = tasks.pop()
print(f"Popped Task: '{last_task}' | Remaining: {tasks}")

popped_second = tasks.pop(1)
print(f"Popped Index 1: '{popped_second}' | Remaining: {tasks}")

# 2. remove() deletes first matching value
tasks.remove("Code")
print("After remove('Code')     :", tasks)

# 3. del keyword
del tasks[0]
print("After del tasks[0]       :", tasks)
OUTPUT
Popped Task: 'Test' | Remaining: ['Code', 'Test', 'Review', 'Deploy']
Popped Index 1: 'Test' | Remaining: ['Code', 'Review', 'Deploy']
After remove('Code')     : ['Review', 'Deploy']
After del tasks[0]       : ['Deploy']

4. Advanced List Slicing and Stride Manipulation

List slicing extracts or modifies sub-sequences without mutating the original source list unless explicit slice assignment is performed.

1. Slicing Syntax Mechanics

sub_list = original_list[start : stop : step]
  • start: Inclusive starting index (defaults to 0).
  • stop: **Exclusive** ending index (defaults to len(list)).
  • step: Stride value increment (defaults to 1). Negative steps step backwards.
LIST_SLICING_DEMO.PY
numbers = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

print("Original Numbers Array:", numbers)
print("Slice [2:6]           :", numbers[2:6])      # Indices 2, 3, 4, 5
print("First 4 items [:4]    :", numbers[:4])       # Indices 0 to 3
print("Items from index 6 [6:]:", numbers[6:])       # Index 6 to end
print("Even Index Step [::2] :", numbers[::2])      # Every 2nd item
print("Reversed Array [::-1]  :", numbers[::-1])     # Negative step reverses!

# In-Place Slice Modification (Replacing a range)
numbers[1:4] = [100, 200]
print("\nAfter Slice Replacement numbers[1:4] = [100, 200]:")
print(numbers)
OUTPUT
Original Numbers Array: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
Slice [2:6]           : [20, 30, 40, 50]
First 4 items [:4]    : [0, 10, 20, 30]
Items from index 6 [6:]: [60, 70, 80, 90]
Even Index Step [::2] : [0, 20, 40, 60, 80]
Reversed Array [::-1]  : [90, 80, 70, 60, 50, 40, 30, 20, 10, 0]

After Slice Replacement numbers[1:4] = [100, 200]:
[0, 100, 200, 400, 500, 600, 700, 800, 900]

5. Memory References: Shallow Copying vs Deep Copying

One of the most frequent sources of subtle bugs in Python applications involves assigning one list variable to another (e.g., b = a).

Assignment Creates References, Not Copies: Writing list_b = list_a does NOT duplicate the list in memory. It simply creates a second variable name pointing to the exact same memory ID object! Modifying list_b will mutate list_a simultaneously!

1. Shallow Copying (`list.copy()`, `list[:]`, `copy.copy()`)

A Shallow Copy creates a new top-level list object, but fills it with references to the elements contained in the original list. If the list contains nested mutable structures (like sub-lists), modifying a nested sub-list will mutate BOTH lists!

2. Deep Copying (`copy.deepcopy()`)

A Deep Copy (imported from standard library copy module) recursively duplicates the top-level list AND all nested mutable objects, creating a completely independent object clone hierarchy.

SHALLOW_VS_DEEP_COPY.PY
import copy

# Original nested matrix list
original = [[1, 2], [3, 4]]

# 1. Assignment (Shared Pointer Reference)
ref_assignment = original

# 2. Shallow Copy
shallow_copy = original.copy()

# 3. Deep Copy
deep_copy = copy.deepcopy(original)

# Mutating top-level and nested structures
original[0][0] = 999  # Mutates nested sub-list element

print("Original List           :", original)
print("Reference Assignment    :", ref_assignment) # Mutated!
print("Shallow Copy (Nested)   :", shallow_copy)   # Mutated nested element!
print("Deep Copy (Independent) :", deep_copy)      # Completely UNTOUCHED!

print("\n--- Object Memory ID Checks ---")
print("Original ID:", id(original))
print("Shallow ID :", id(shallow_copy), "(Different Top ID)")
print("Deep ID    :", id(deep_copy), "(Different Top & Nested ID)")
OUTPUT
Original List           : [[999, 2], [3, 4]]
Reference Assignment    : [[999, 2], [3, 4]]
Shallow Copy (Nested)   : [[999, 2], [3, 4]]
Deep Copy (Independent) : [[1, 2], [3, 4]]

--- Object Memory ID Checks ---
Original ID: 140239482910224
Shallow ID : 140239482911856 (Different Top ID)
Deep ID    : 140239482912496 (Different Top & Nested ID)

6. Integrating List Operations with `match-case` Pattern Matching

Combining list manipulation methods with match-case structural sequence pattern matching creates clean API dispatch handlers capable of processing list-based queue operations.

MATCH_CASE_LIST_DISPATCHER.PY
def process_inventory_queue(command_list, inventory_state):
    """
    Processes list-based batch commands using Structural Pattern Matching.
    Demonstrates updating list state dynamically.
    """
    match command_list:
        case ["ADD", *items] if len(items) > 0:
            inventory_state.extend(items)
            return f"SUCCESS: Added {len(items)} items -> {items}"
            
        case ["REMOVE", target_item] if target_item in inventory_state:
            inventory_state.remove(target_item)
            return f"SUCCESS: Removed '{target_item}' from inventory."
            
        case ["REMOVE", target_item]:
            return f"WARNING: Item '{target_item}' not found in inventory."
            
        case ["POP_FRONT"]:
            if inventory_state:
                removed = inventory_state.pop(0)
                return f"SUCCESS: Popped front item '{removed}'."
            return "WARNING: Inventory is empty."
            
        case ["CLEAR"]:
            inventory_state.clear()
            return "SUCCESS: Inventory completely cleared."
            
        case _:
            return "ERROR: Unrecognized list operation command."

# Testing the dispatcher
state = ["Monitor", "Keyboard"]

print(process_inventory_queue(["ADD", "Mouse", "Webcam"], state))
print(f"Active State: {state}")

print(process_inventory_queue(["REMOVE", "Keyboard"], state))
print(f"Active State: {state}")

print(process_inventory_queue(["POP_FRONT"], state))
print(f"Final State : {state}")
OUTPUT
SUCCESS: Added 2 items -> ['Mouse', 'Webcam']
Active State: ['Monitor', 'Keyboard', 'Mouse', 'Webcam']
SUCCESS: Removed 'Keyboard' from inventory.
Active State: ['Monitor', 'Mouse', 'Webcam']
SUCCESS: Popped front item 'Monitor'.
Final State : ['Mouse', 'Webcam']

7. Frequently Asked Interview Questions with Answers

Q1: How does Python store lists in memory, and why are they heterogeneous?
Answer: Python lists are implemented as contiguous arrays of **object references (pointers)** rather than contiguous primitive byte values. Because each pointer element references an independent Python object stored on the Heap, a single list can reference objects of completely different data types simultaneously.
Q2: What is the time complexity difference between `.append()` and `.insert(0, item)`?
Answer: .append()` takes **$O(1)$ amortized constant time** because items are added to the end without moving existing elements. .insert(0, item) takes **$O(n)$ linear time** because every existing element in the list must be shifted one position to the right in memory.

Q3: What is the difference between `.append(list_b)` and `.extend(list_b)`?
Answer: .append(list_b) adds list_b as a single nested list element at the end of the list. .extend(list_b) iterates through list_b and appends every individual item to the end of the list, flattening the insertion.
Q4: What is the difference between Shallow Copy and Deep Copy?
Answer: A Shallow Copy (a.copy()) duplicates the top-level list container but retains references to nested child objects. If nested objects (like sub-lists) are mutated, both original and shallow copies change. A Deep Copy (copy.deepcopy(a)) recursively duplicates all nested structures, producing a completely independent object graph.
Q5: How do `.pop()` and `.remove()` differ when deleting list elements?
Answer: .pop(index) deletes an element by its zero-based **index position** and returns the deleted value. .remove(value) searches left-to-right and deletes the first matching **value occurrence**, returning None.

8. Homework & Practical Assignments

Task 1: List Mutation and Slicing Utilities

Create a script named list_basics_task.py inside your lesson_21 folder:

  • Define a list of numbers: nums = [10, 20, 30, 40, 50, 60, 70, 80].
  • Use slice notation to extract the middle four numbers into a new list.
  • Use .insert() to add 25 at index 2.
  • Use .pop() to remove the last element and .remove() to delete 10.
  • Print the final mutated list and its length using len().

Task 2: Safe List Copying & Nested Matrix Mutation

Create a script named safe_matrix_copy.py:

  • Import the copy module.
  • Define a nested matrix list: matrix = [["A", "B"], ["C", "D"]].
  • Create a shallow copy and a deep copy of matrix.
  • Mutate element matrix[0][0] = "Z".
  • Print all three matrices using f-strings to prove deep copy isolation.

Task 3: Master Capstone Project — Task Queue Management Engine (`task_manager_os.py`)

Project Goal: Build a complete, modular, interactive terminal script named task_manager_os.py integrating **ALL concepts learned across Lessons 1 through 21**.

Project Architectural Requirements Specification:

  1. Environment, Modules & Entry Guard (Lessons 18-20):
    • Import standard library modules (sys, copy, datetime).
    • Wrap main application execution inside an if __name__ == "__main__": guard block.
  2. List Data Structure Operations (Lesson 21):
    • Maintain a global dynamic task queue list: TASK_QUEUE = [].
    • Support list mutations (append, insert, pop, remove, extend).
    • Implement shallow and deep copy snapshot history exports using copy.deepcopy().
  3. Functional Tools & Anonymous Lambdas (Lesson 17):
    • Filter active task lists using filter() and lambda functions.
    • Sort task items by priority using sorted() with custom key functions.
  4. Advanced Function Parameters & Scope (Lessons 15-16):
    • Structure code into pure modular functions accepting *args and **kwargs.
    • Use safe default parameters and early return Guard Clauses.
  5. Indefinite & Definite Loops (Lessons 13-14):
    • Run the interactive CLI interface inside a continuous while True menu loop.
    • Iterate through output records using for loops with enumerate().
  6. Pattern Matching CLI Dispatcher (Lesson 12):
    • Process incoming user CLI command tokens inside a match-case block:
      • case ["PUSH", *tasks] → Extend active queue with new tasks.
      • case ["INSERT", idx_str, task_name] if idx_str.isdigit() → Insert task at index.
      • case ["POP", idx_str] if idx_str.isdigit() → Pop task by index.
      • case ["SNAPSHOT"] → Create deep copy backup of current state.
      • case ["EXIT" | "QUIT"] → Terminate session safely using a sentinel flag.
      • case _ → Output command error message.
  7. Conditionals, Formatting & Foundations (Lessons 1-11):
    • Sanitize all inputs using .strip() and .upper().
    • Format output tabular lists using f-strings with alignment specifiers (:<15, :>10) and clear visual borders.

9. File & Workspace Directory Structure

Standard Course Workspace Layout:

Ensure all exercise files are stored inside their corresponding lesson directories:

python_mastery_course/
│
├── lesson_01/ ... lesson_20/
│
└── lesson_21/
    ├── list_creation_demo.py
    ├── list_mutation_insertion.py
    ├── list_mutation_deletion.py
    ├── list_slicing_demo.py
    ├── shallow_vs_deep_copy.py
    ├── match_case_list_dispatcher.py
    ├── list_basics_task.py        <-- (Task 1)
    ├── safe_matrix_copy.py        <-- (Task 2)
    └── task_manager_os.py         <-- (Task 3: Master Review Capstone)
        

10. What We Will Learn Next

Next Up: Lesson 22 — Tuples and Sequence Unpacking

Now that you master mutable list operations, slicing, and memory copying, we will explore Python's primary immutable sequence structure: Tuples!

In the next lesson, we will cover:

  • Creating immutable Tuples (`tuple`) and single-element tuple syntax rules.
  • Why immutability matters: Memory performance, hashability, and thread safety.
  • Sequence Unpacking mechanics (e.g., a, b, c = my_tuple).
  • Extended Unpacking with the star operator (head, *tail = items).
  • Named Tuples (collections.namedtuple) for self-documenting data structures.

📝 Live Lesson Practice

HTML/CSS JavaScript Python C++ C PHP
⌨️ Practice Inputs (लाइव इनपुट भरें) (खाली होने पर RED, भरने पर GREEN underline)
💻 Code Editor (Monaco VS Code Engine)
👀 Live Preview
Address Contact

+91 7877547686

E-mail

onlinecbtportal@gmail.com

Helpline Number

+91 7877547686


Click To Download
Get it on Google Play

ऐप डाउनलोड करने
के लिए Google Play पर
उपलब्ध है

WhatsApp Chat