Skip to main content

📋 Lesson 5.1: Arrays and Lists

So far, every variable has held a single value. But real programs work with collections — lists of names, arrays of scores, queues of tasks. This lesson introduces the most fundamental collection type: the ordered list.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Create arrays and lists in all three languages
  • Access elements by index, including negative indexing
  • Add, remove, and modify elements using common methods
  • Use slicing to extract portions of a list
  • Understand the difference between C# arrays and List<T>
  • Write list comprehensions in Python

Estimated Time: 45 minutes

Project: Build a to-do list manager with add, remove, and search operations

📑 In This Lesson

Creating Arrays and Lists

An array (or list) is an ordered collection of values stored under a single name. Each value has a numbered position called an index, starting at 0.

# Python uses "lists" — created with square brackets
fruits = ["apple", "banana", "cherry"]
numbers = [10, 20, 30, 40, 50]
mixed = ["hello", 42, True, 3.14]  # Python lists can mix types

# Empty list
empty = []

# List from a range
one_to_ten = list(range(1, 11))  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Repeated values
zeros = [0] * 5  # [0, 0, 0, 0, 0]

print(fruits)        # ['apple', 'banana', 'cherry']
print(len(fruits))   # 3
print(type(fruits))  # <class 'list'>
// JavaScript uses "arrays" — created with square brackets
let fruits = ["apple", "banana", "cherry"];
let numbers = [10, 20, 30, 40, 50];
let mixed = ["hello", 42, true, 3.14];  // JS arrays can mix types

// Empty array
let empty = [];

// Array from a range (no built-in range, use Array.from)
let oneToTen = Array.from({length: 10}, (_, i) => i + 1);

// Repeated values
let zeros = new Array(5).fill(0);  // [0, 0, 0, 0, 0]

console.log(fruits);         // ['apple', 'banana', 'cherry']
console.log(fruits.length);  // 3
console.log(typeof fruits);  // 'object' (arrays are objects in JS)
console.log(Array.isArray(fruits));  // true
// C# has two main options: arrays (fixed size) and List<T> (resizable)

// Arrays — fixed size, declared with type[]
string[] fruits = { "apple", "banana", "cherry" };
int[] numbers = { 10, 20, 30, 40, 50 };

// List<T> — resizable, more flexible (like Python/JS)
List<string> fruitList = new List<string> { "apple", "banana", "cherry" };
List<int> numberList = new List<int> { 10, 20, 30, 40, 50 };

// Empty list
List<string> empty = new List<string>();

// Array from a range
int[] oneToTen = Enumerable.Range(1, 10).ToArray();

// Repeated values
int[] zeros = new int[5];  // All zeros by default

Console.WriteLine(string.Join(", ", fruits));  // apple, banana, cherry
Console.WriteLine(fruits.Length);               // 3 (array)
Console.WriteLine(fruitList.Count);             // 3 (List<T>)

// ⚠️ C# collections are strongly typed — no mixing types!

💡 Terminology

Python calls them lists, JavaScript calls them arrays, and C# has both arrays (fixed-size) and Lists (resizable). They all serve the same purpose: storing an ordered sequence of items. In this lesson, we'll use "list" and "array" interchangeably when talking about the concept.

🎓 Instructor Note: Delivery Guidance

This is the beginning of Module 5, so it's a natural reset point. Students who were comfortable with functions might find collections initially more intuitive — it's a concrete, visual concept. Draw a list as a row of boxes with index numbers above them. Emphasize zero-based indexing early: "The first item is at index 0, not index 1." This trips up almost every beginner at least once. For C#, stick primarily to List<T> for now — it behaves most like Python lists and JS arrays. Cover arrays briefly but note that List<T> is what they'll use most.

Accessing Elements

Every element has a numeric index. The first element is at index 0, the second at 1, and so on.

colors = ["red", "green", "blue", "yellow", "purple"]
#  Index:   0       1       2        3         4

# Access by index
print(colors[0])    # "red"     (first element)
print(colors[2])    # "blue"    (third element)
print(colors[4])    # "purple"  (last element)

# Negative indexing — count from the end!
print(colors[-1])   # "purple"  (last)
print(colors[-2])   # "yellow"  (second to last)
print(colors[-3])   # "blue"

# Out of range = error
# print(colors[10])  # ❌ IndexError: list index out of range

# Get the length
print(len(colors))           # 5
print(colors[len(colors)-1]) # "purple" (last, the hard way)
print(colors[-1])            # "purple" (last, the easy way)
let colors = ["red", "green", "blue", "yellow", "purple"];
//  Index:      0       1       2        3         4

// Access by index
console.log(colors[0]);    // "red"
console.log(colors[2]);    // "blue"
console.log(colors[4]);    // "purple"

// Negative indexing with .at() (modern JS)
console.log(colors.at(-1));   // "purple"
console.log(colors.at(-2));   // "yellow"

// Out of range = undefined (not an error!)
console.log(colors[10]);   // undefined
console.log(colors[-1]);   // undefined (use .at() instead!)

// Get the length
console.log(colors.length);                  // 5
console.log(colors[colors.length - 1]);      // "purple" (classic way)
console.log(colors.at(-1));                  // "purple" (modern way)
List<string> colors = new List<string>
    { "red", "green", "blue", "yellow", "purple" };
//    Index: 0       1       2        3         4

// Access by index
Console.WriteLine(colors[0]);    // "red"
Console.WriteLine(colors[2]);    // "blue"
Console.WriteLine(colors[4]);    // "purple"

// Negative indexing with ^ (Index from End operator)
Console.WriteLine(colors[^1]);   // "purple" (last)
Console.WriteLine(colors[^2]);   // "yellow" (second to last)

// Out of range = exception
// Console.WriteLine(colors[10]);  // ❌ ArgumentOutOfRangeException

// Get the length
Console.WriteLine(colors.Count);               // 5
Console.WriteLine(colors[colors.Count - 1]);   // "purple" (classic)
Console.WriteLine(colors[^1]);                 // "purple" (modern)

⚠️ Zero-Based Indexing

All three languages start counting at 0, not 1. In a list of 5 items, valid indices are 0 through 4. This is one of the most common beginner mistakes — always remember that the last valid index is length - 1.

Negative Indexing Quick Reference

Access Pattern 🐍 Python ⚡ JavaScript 🔷 C#
Last element list[-1] arr.at(-1) list[^1]
Second to last list[-2] arr.at(-2) list[^2]
Out of bounds IndexError undefined Exception

Modifying Elements

You can change any element by assigning a new value to its index.

scores = [85, 92, 78, 90, 88]

# Change a single element
scores[2] = 95
print(scores)  # [85, 92, 95, 90, 88]

# Change the last element
scores[-1] = 100
print(scores)  # [85, 92, 95, 90, 100]

# Swap two elements
scores[0], scores[1] = scores[1], scores[0]
print(scores)  # [92, 85, 95, 90, 100]
let scores = [85, 92, 78, 90, 88];

// Change a single element
scores[2] = 95;
console.log(scores);  // [85, 92, 95, 90, 88]

// Change the last element
scores[scores.length - 1] = 100;
console.log(scores);  // [85, 92, 95, 90, 100]

// Swap two elements (destructuring)
[scores[0], scores[1]] = [scores[1], scores[0]];
console.log(scores);  // [92, 85, 95, 90, 100]
List<int> scores = new List<int> { 85, 92, 78, 90, 88 };

// Change a single element
scores[2] = 95;
Console.WriteLine(string.Join(", ", scores));  // 85, 92, 95, 90, 88

// Change the last element
scores[^1] = 100;
Console.WriteLine(string.Join(", ", scores));  // 85, 92, 95, 90, 100

// Swap two elements
(scores[0], scores[1]) = (scores[1], scores[0]);
Console.WriteLine(string.Join(", ", scores));  // 92, 85, 95, 90, 100

Adding and Removing Elements

This is where lists really shine — you can grow and shrink them dynamically.

Adding Elements

fruits = ["apple", "banana"]

# Add to the end
fruits.append("cherry")
print(fruits)  # ['apple', 'banana', 'cherry']

# Add multiple items to the end
fruits.extend(["date", "elderberry"])
print(fruits)  # ['apple', 'banana', 'cherry', 'date', 'elderberry']

# Insert at a specific index
fruits.insert(1, "blueberry")  # Insert at index 1
print(fruits)  # ['apple', 'blueberry', 'banana', 'cherry', 'date', 'elderberry']

# Concatenate lists (creates a new list)
more = fruits + ["fig", "grape"]
print(more)  # [..., 'fig', 'grape']
let fruits = ["apple", "banana"];

// Add to the end
fruits.push("cherry");
console.log(fruits);  // ['apple', 'banana', 'cherry']

// Add multiple items to the end
fruits.push("date", "elderberry");
console.log(fruits);  // ['apple', 'banana', 'cherry', 'date', 'elderberry']

// Add to the beginning
fruits.unshift("apricot");
console.log(fruits);  // ['apricot', 'apple', 'banana', ...]

// Insert at a specific index (splice)
fruits.splice(2, 0, "blueberry");  // At index 2, delete 0, insert "blueberry"
console.log(fruits);

// Concatenate arrays (creates a new array)
let more = [...fruits, "fig", "grape"];  // Spread operator
// or: let more = fruits.concat(["fig", "grape"]);
List<string> fruits = new List<string> { "apple", "banana" };

// Add to the end
fruits.Add("cherry");
Console.WriteLine(string.Join(", ", fruits));  // apple, banana, cherry

// Add multiple items to the end
fruits.AddRange(new[] { "date", "elderberry" });

// Insert at a specific index
fruits.Insert(1, "blueberry");  // Insert at index 1

// Concatenate lists (creates a new list)
List<string> more = new List<string>(fruits);
more.AddRange(new[] { "fig", "grape" });

Removing Elements

items = ["a", "b", "c", "d", "e"]

# Remove by value (first occurrence)
items.remove("c")
print(items)  # ['a', 'b', 'd', 'e']

# Remove by index and get the value back
removed = items.pop(1)    # Remove index 1
print(removed)            # 'b'
print(items)              # ['a', 'd', 'e']

# Remove last element
last = items.pop()        # No index = last item
print(last)               # 'e'
print(items)              # ['a', 'd']

# Remove by index (without getting value back)
del items[0]
print(items)              # ['d']

# Clear all elements
items.clear()
print(items)              # []
let items = ["a", "b", "c", "d", "e"];

// Remove last element
let last = items.pop();
console.log(last);    // 'e'
console.log(items);   // ['a', 'b', 'c', 'd']

// Remove first element
let first = items.shift();
console.log(first);   // 'a'
console.log(items);   // ['b', 'c', 'd']

// Remove by index (splice)
let removed = items.splice(1, 1);  // At index 1, remove 1 element
console.log(removed); // ['c']
console.log(items);   // ['b', 'd']

// Remove by value (find index first)
let colors = ["red", "green", "blue"];
let idx = colors.indexOf("green");
if (idx !== -1) colors.splice(idx, 1);
console.log(colors);  // ['red', 'blue']

// Clear all elements
items.length = 0;  // or items = [];
List<string> items = new List<string> { "a", "b", "c", "d", "e" };

// Remove by value (first occurrence)
items.Remove("c");
Console.WriteLine(string.Join(", ", items));  // a, b, d, e

// Remove by index
items.RemoveAt(1);  // Remove index 1 ("b")
Console.WriteLine(string.Join(", ", items));  // a, d, e

// Remove last element
items.RemoveAt(items.Count - 1);
Console.WriteLine(string.Join(", ", items));  // a, d

// Clear all elements
items.Clear();
Console.WriteLine(items.Count);  // 0

Adding & Removing Quick Reference

Operation 🐍 Python ⚡ JavaScript 🔷 C#
Add to end .append(x) .push(x) .Add(x)
Add to beginning .insert(0, x) .unshift(x) .Insert(0, x)
Insert at index .insert(i, x) .splice(i, 0, x) .Insert(i, x)
Remove last .pop() .pop() .RemoveAt(Count-1)
Remove first .pop(0) .shift() .RemoveAt(0)
Remove by value .remove(x) .splice(indexOf(x), 1) .Remove(x)
🎓 Instructor Note: Delivery Guidance

JavaScript's splice is the trickiest method here — it does insertion, deletion, and replacement depending on arguments. Walk through the three-argument form: splice(startIndex, deleteCount, ...itemsToInsert). Python's methods are the most intuitive by name. Call out the pattern that Python and C# have remove by value, while JS requires a two-step indexOf + splice — a common source of frustration. The reference table is great for students to bookmark.

Slicing

Slicing extracts a portion of a list into a new list. It's one of the most useful operations for working with collections.

letters = ["a", "b", "c", "d", "e", "f"]
#  Index:    0     1     2     3     4     5

# slice[start:end] — start is included, end is excluded
print(letters[1:4])    # ['b', 'c', 'd']  (index 1, 2, 3)
print(letters[0:3])    # ['a', 'b', 'c']  (index 0, 1, 2)

# Omit start = from beginning
print(letters[:3])     # ['a', 'b', 'c']

# Omit end = to the end
print(letters[3:])     # ['d', 'e', 'f']

# Negative indices in slices
print(letters[-3:])    # ['d', 'e', 'f']  (last 3)
print(letters[:-2])    # ['a', 'b', 'c', 'd']  (all but last 2)

# Step: slice[start:end:step]
print(letters[::2])    # ['a', 'c', 'e']  (every other)
print(letters[::-1])   # ['f', 'e', 'd', 'c', 'b', 'a']  (reversed!)

# Slicing creates a COPY (safe to modify)
first_three = letters[:3]
first_three[0] = "Z"
print(letters[0])      # 'a' — original unchanged
let letters = ["a", "b", "c", "d", "e", "f"];

// .slice(start, end) — start included, end excluded
console.log(letters.slice(1, 4));    // ['b', 'c', 'd']
console.log(letters.slice(0, 3));    // ['a', 'b', 'c']

// Omit end = to the end
console.log(letters.slice(3));       // ['d', 'e', 'f']

// Negative indices
console.log(letters.slice(-3));      // ['d', 'e', 'f'] (last 3)
console.log(letters.slice(0, -2));   // ['a', 'b', 'c', 'd'] (all but last 2)

// No built-in step — use filter for every-other:
let everyOther = letters.filter((_, i) => i % 2 === 0);
console.log(everyOther);  // ['a', 'c', 'e']

// Reverse (modifies in place!)
let reversed = [...letters].reverse();  // Spread first to avoid mutating
console.log(reversed);  // ['f', 'e', 'd', 'c', 'b', 'a']

// slice() creates a COPY
let firstThree = letters.slice(0, 3);
firstThree[0] = "Z";
console.log(letters[0]);  // 'a' — original unchanged

// ⚠️ slice() copies, splice() mutates — don't confuse them!
List<string> letters = new List<string>
    { "a", "b", "c", "d", "e", "f" };

// GetRange(startIndex, count) — note: count, not end index!
List<string> middle = letters.GetRange(1, 3);  // 3 items from index 1
Console.WriteLine(string.Join(", ", middle));    // b, c, d

// Range operator with arrays: [start..end]
string[] arr = { "a", "b", "c", "d", "e", "f" };
string[] slice = arr[1..4];  // index 1, 2, 3
Console.WriteLine(string.Join(", ", slice));  // b, c, d

// From end
string[] lastThree = arr[3..];    // ['d', 'e', 'f']
string[] firstThree = arr[..3];   // ['a', 'b', 'c']
string[] skipLast2 = arr[..^2];   // ['a', 'b', 'c', 'd']

// Reverse
List<string> reversed = new List<string>(letters);
reversed.Reverse();
Console.WriteLine(string.Join(", ", reversed));  // f, e, d, c, b, a

// GetRange creates a copy
List<string> copy = letters.GetRange(0, 3);
copy[0] = "Z";
Console.WriteLine(letters[0]);  // 'a' — original unchanged

⚠️ JavaScript: slice() vs. splice()

slice() copies a portion — the original array is unchanged. splice() modifies the original array (adds/removes elements). One letter difference, completely different behavior. This is a classic JavaScript gotcha.

Searching and Checking

Common tasks: check if an item exists, find its position, or count occurrences.

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

# Check if item exists
print("green" in colors)      # True
print("purple" in colors)     # False
print("purple" not in colors) # True

# Find index of first occurrence
print(colors.index("green"))  # 1
# colors.index("purple")      # ❌ ValueError if not found!

# Safe search
if "purple" in colors:
    print(colors.index("purple"))
else:
    print("Not found")

# Count occurrences
print(colors.count("green"))  # 2
print(colors.count("red"))    # 1

# Sort
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort()                     # In-place sort
print(numbers)                     # [1, 1, 2, 3, 4, 5, 6, 9]

sorted_copy = sorted([3, 1, 4])   # New sorted list
print(sorted_copy)                 # [1, 3, 4]
let colors = ["red", "green", "blue", "green", "yellow"];

// Check if item exists
console.log(colors.includes("green"));   // true
console.log(colors.includes("purple"));  // false

// Find index of first occurrence
console.log(colors.indexOf("green"));    // 1
console.log(colors.indexOf("purple"));   // -1 (not found)

// Find with a condition
let found = colors.find(c => c.startsWith("g"));
console.log(found);  // "green" (first match)

let foundIndex = colors.findIndex(c => c.startsWith("y"));
console.log(foundIndex);  // 4

// Count occurrences (no built-in — use filter)
let greenCount = colors.filter(c => c === "green").length;
console.log(greenCount);  // 2

// Sort
let numbers = [3, 1, 4, 1, 5, 9, 2, 6];
numbers.sort((a, b) => a - b);  // ⚠️ Must provide comparator for numbers!
console.log(numbers);            // [1, 1, 2, 3, 4, 5, 6, 9]

// Without comparator, sort is alphabetical:
// [3, 1, 4, 11].sort() → [1, 11, 3, 4]  ← WRONG for numbers!
List<string> colors = new List<string>
    { "red", "green", "blue", "green", "yellow" };

// Check if item exists
Console.WriteLine(colors.Contains("green"));   // True
Console.WriteLine(colors.Contains("purple"));  // False

// Find index of first occurrence
Console.WriteLine(colors.IndexOf("green"));    // 1
Console.WriteLine(colors.IndexOf("purple"));   // -1

// Find with a condition
string? found = colors.Find(c => c.StartsWith("g"));
Console.WriteLine(found);  // "green"

int foundIndex = colors.FindIndex(c => c.StartsWith("y"));
Console.WriteLine(foundIndex);  // 4

// Count occurrences
int greenCount = colors.Count(c => c == "green");  // LINQ
Console.WriteLine(greenCount);  // 2

// Sort
List<int> numbers = new List<int> { 3, 1, 4, 1, 5, 9, 2, 6 };
numbers.Sort();
Console.WriteLine(string.Join(", ", numbers));  // 1, 1, 2, 3, 4, 5, 6, 9

⚠️ JavaScript's Sort Gotcha

JavaScript's default .sort() converts elements to strings and sorts alphabetically. This means [3, 1, 11].sort() gives [1, 11, 3] — not what you want for numbers! Always pass a comparator: .sort((a, b) => a - b) for ascending numeric sort.

C# Arrays vs. List<T>

C# gives you two main collection types. Understanding when to use each is important.

# Python only has lists — they're always resizable
# No need to choose between fixed and dynamic

numbers = [1, 2, 3]
numbers.append(4)    # Always works — lists grow automatically
numbers.pop()        # Always works — lists shrink automatically

# Python's list IS the dynamic array
# (Closest to C#'s List<T>)

# If you need a fixed-size array (rare), use a tuple:
fixed = (1, 2, 3)   # Immutable — can't add, remove, or change elements
# fixed[0] = 99     # ❌ TypeError: 'tuple' object does not support assignment
// JavaScript only has arrays — they're always resizable
// No need to choose between fixed and dynamic

let numbers = [1, 2, 3];
numbers.push(4);    // Always works
numbers.pop();      // Always works

// JavaScript arrays are like Python lists / C#'s List<T>

// If you need a fixed-size collection (rare), you can freeze:
const fixed = Object.freeze([1, 2, 3]);
// fixed.push(4);   // ❌ TypeError in strict mode
// fixed[0] = 99;   // ❌ TypeError in strict mode
// ARRAYS: Fixed size, slightly faster, use when size won't change
int[] scores = new int[5];       // 5 slots, all start at 0
scores[0] = 95;
scores[1] = 87;
// scores[5] = 100;             // ❌ IndexOutOfRangeException
// Can't add or remove — size is locked at creation

// LIST<T>: Resizable, more methods, use for most things
List<int> scoreList = new List<int>();
scoreList.Add(95);
scoreList.Add(87);
scoreList.Add(100);   // Grows automatically!
scoreList.Remove(87); // Shrinks automatically!

// Convert between them:
int[] fromList = scoreList.ToArray();
List<int> fromArray = new List<int>(scores);

// WHEN TO USE WHICH:
// Array:  Fixed-size data (days of week, RGB values, game board)
//         Performance-critical inner loops
// List<T>: Everything else (user input, dynamic data, most cases)

// Rule of thumb: Start with List<T>. Use arrays only when
// you know the size won't change and you need the performance.

💡 The Bottom Line

Python and JavaScript each have one list/array type that handles everything. C# gives you the choice between arrays (fixed, fast) and List<T> (flexible, feature-rich). For beginners: use List<T> in C# — it works like Python lists and JS arrays. You can always switch to arrays later for performance-critical code.

List Comprehensions (Python)

Python has a powerful shorthand for creating new lists by transforming or filtering existing ones. It's called a list comprehension, and once you learn it, you'll use it everywhere.

# Basic: [expression for item in iterable]
numbers = [1, 2, 3, 4, 5]

# Double each number
doubled = [n * 2 for n in numbers]
print(doubled)  # [2, 4, 6, 8, 10]

# Square each number
squares = [n ** 2 for n in numbers]
print(squares)  # [1, 4, 9, 16, 25]

# With filter: [expression for item in iterable if condition]
evens = [n for n in numbers if n % 2 == 0]
print(evens)  # [2, 4]

# Transform strings
names = ["alice", "bob", "charlie"]
upper = [name.upper() for name in names]
print(upper)  # ['ALICE', 'BOB', 'CHARLIE']

# Filter and transform
long_upper = [name.upper() for name in names if len(name) > 3]
print(long_upper)  # ['ALICE', 'CHARLIE']

# Equivalent loop (comprehension is more concise):
result = []
for name in names:
    if len(name) > 3:
        result.append(name.upper())
# Same as: [name.upper() for name in names if len(name) > 3]
// JavaScript doesn't have list comprehensions
// Instead, use .map() and .filter() (very common!)

let numbers = [1, 2, 3, 4, 5];

// Double each number (.map transforms each element)
let doubled = numbers.map(n => n * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

// Square each number
let squares = numbers.map(n => n ** 2);
console.log(squares);  // [1, 4, 9, 16, 25]

// Filter (.filter keeps elements that pass a test)
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens);  // [2, 4]

// Transform strings
let names = ["alice", "bob", "charlie"];
let upper = names.map(name => name.toUpperCase());
console.log(upper);  // ['ALICE', 'BOB', 'CHARLIE']

// Filter and transform (chain them!)
let longUpper = names
    .filter(name => name.length > 3)
    .map(name => name.toUpperCase());
console.log(longUpper);  // ['ALICE', 'CHARLIE']
// C# uses LINQ (Language Integrated Query) for similar operations
using System.Linq;

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Double each number (.Select = map)
List<int> doubled = numbers.Select(n => n * 2).ToList();
Console.WriteLine(string.Join(", ", doubled));  // 2, 4, 6, 8, 10

// Square each number
List<int> squares = numbers.Select(n => n * n).ToList();

// Filter (.Where = filter)
List<int> evens = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", evens));  // 2, 4

// Transform strings
List<string> names = new List<string> { "alice", "bob", "charlie" };
List<string> upper = names.Select(n => n.ToUpper()).ToList();

// Filter and transform (chain them!)
List<string> longUpper = names
    .Where(name => name.Length > 3)
    .Select(name => name.ToUpper())
    .ToList();
Console.WriteLine(string.Join(", ", longUpper));  // ALICE, CHARLIE

Transform & Filter Quick Reference

Operation 🐍 Python ⚡ JavaScript 🔷 C#
Transform each [expr for x in list] .map(x => expr) .Select(x => expr)
Filter [x for x in list if cond] .filter(x => cond) .Where(x => cond)
Filter + transform [expr for x in list if cond] .filter().map() .Where().Select()
🎓 Instructor Note: Delivery Guidance

List comprehensions are a Python superpower, but they can look intimidating. Start with the simplest form: [n * 2 for n in numbers] and read it aloud: "for each n in numbers, give me n times 2." Then add the filter. The equivalence table is key — students should see that Python's [...], JavaScript's .map()/.filter(), and C#'s LINQ .Select()/.Where() all do the same thing with different syntax. We'll revisit iteration methods in Lesson 15.

Exercises

🏋️ Exercise 1: Basic List Operations

Objective: Create a list of 5 favorite movies. Then:

  • Print the first and last movie
  • Add a 6th movie to the end
  • Insert a movie at position 2
  • Remove a movie by name
  • Print the final list and its length
✅ Solution
movies = ["Inception", "The Matrix", "Interstellar", "Dune", "Arrival"]

print(movies[0])     # Inception
print(movies[-1])    # Arrival

movies.append("Blade Runner 2049")
movies.insert(2, "Tenet")
movies.remove("Dune")

print(movies)        # ['Inception', 'The Matrix', 'Tenet', 'Interstellar', 'Arrival', 'Blade Runner 2049']
print(len(movies))   # 6
let movies = ["Inception", "The Matrix", "Interstellar", "Dune", "Arrival"];

console.log(movies[0]);       // Inception
console.log(movies.at(-1));   // Arrival

movies.push("Blade Runner 2049");
movies.splice(2, 0, "Tenet");
let idx = movies.indexOf("Dune");
if (idx !== -1) movies.splice(idx, 1);

console.log(movies);
console.log(movies.length);  // 6
List<string> movies = new List<string>
    { "Inception", "The Matrix", "Interstellar", "Dune", "Arrival" };

Console.WriteLine(movies[0]);    // Inception
Console.WriteLine(movies[^1]);   // Arrival

movies.Add("Blade Runner 2049");
movies.Insert(2, "Tenet");
movies.Remove("Dune");

Console.WriteLine(string.Join(", ", movies));
Console.WriteLine(movies.Count);  // 6

🏋️ Exercise 2: List Statistics

Objective: Given a list of test scores, write code to find:

  • The highest and lowest score
  • The average score
  • How many scores are above the average
  • A sorted copy of the scores (don't modify the original)
✅ Solution
scores = [88, 72, 95, 64, 83, 91, 76, 98, 70, 85]

highest = max(scores)
lowest = min(scores)
average = sum(scores) / len(scores)
above_avg = len([s for s in scores if s > average])
sorted_scores = sorted(scores)

print(f"Highest: {highest}")          # 98
print(f"Lowest: {lowest}")            # 64
print(f"Average: {average:.1f}")      # 82.2
print(f"Above average: {above_avg}")  # 5
print(f"Sorted: {sorted_scores}")
print(f"Original: {scores}")          # Unchanged!
let scores = [88, 72, 95, 64, 83, 91, 76, 98, 70, 85];

let highest = Math.max(...scores);
let lowest = Math.min(...scores);
let average = scores.reduce((sum, s) => sum + s, 0) / scores.length;
let aboveAvg = scores.filter(s => s > average).length;
let sortedScores = [...scores].sort((a, b) => a - b);

console.log(`Highest: ${highest}`);          // 98
console.log(`Lowest: ${lowest}`);            // 64
console.log(`Average: ${average.toFixed(1)}`); // 82.2
console.log(`Above average: ${aboveAvg}`);   // 5
console.log(`Sorted: ${sortedScores}`);
console.log(`Original: ${scores}`);           // Unchanged!
List<int> scores = new List<int>
    { 88, 72, 95, 64, 83, 91, 76, 98, 70, 85 };

int highest = scores.Max();
int lowest = scores.Min();
double average = scores.Average();
int aboveAvg = scores.Count(s => s > average);
List<int> sortedScores = scores.OrderBy(s => s).ToList();

Console.WriteLine($"Highest: {highest}");          // 98
Console.WriteLine($"Lowest: {lowest}");            // 64
Console.WriteLine($"Average: {average:F1}");       // 82.2
Console.WriteLine($"Above average: {aboveAvg}");   // 5
Console.WriteLine($"Sorted: {string.Join(", ", sortedScores)}");
Console.WriteLine($"Original: {string.Join(", ", scores)}");

🏋️ Exercise 3: To-Do List Manager

Objective: Build functions for a to-do list that can:

  • add_task(tasks, task) — add a task (reject duplicates)
  • remove_task(tasks, task) — remove a task (handle "not found" gracefully)
  • search_tasks(tasks, keyword) — return all tasks containing the keyword (case-insensitive)
  • show_tasks(tasks) — display all tasks numbered 1, 2, 3…
✅ Solution
def add_task(tasks, task):
    if task in tasks:
        print(f"'{task}' already exists!")
        return
    tasks.append(task)
    print(f"Added: '{task}'")

def remove_task(tasks, task):
    if task not in tasks:
        print(f"'{task}' not found!")
        return
    tasks.remove(task)
    print(f"Removed: '{task}'")

def search_tasks(tasks, keyword):
    keyword_lower = keyword.lower()
    return [t for t in tasks if keyword_lower in t.lower()]

def show_tasks(tasks):
    if not tasks:
        print("No tasks!")
        return
    for i, task in enumerate(tasks, 1):
        print(f"  {i}. {task}")

# Test it out
todo = []
add_task(todo, "Buy groceries")
add_task(todo, "Finish Python lesson")
add_task(todo, "Buy birthday gift")
add_task(todo, "Buy groceries")  # Duplicate!

show_tasks(todo)
# 1. Buy groceries
# 2. Finish Python lesson
# 3. Buy birthday gift

results = search_tasks(todo, "buy")
print(f"Search 'buy': {results}")  # ['Buy groceries', 'Buy birthday gift']

remove_task(todo, "Finish Python lesson")
show_tasks(todo)
function addTask(tasks, task) {
    if (tasks.includes(task)) {
        console.log(`'${task}' already exists!`);
        return;
    }
    tasks.push(task);
    console.log(`Added: '${task}'`);
}

function removeTask(tasks, task) {
    let idx = tasks.indexOf(task);
    if (idx === -1) {
        console.log(`'${task}' not found!`);
        return;
    }
    tasks.splice(idx, 1);
    console.log(`Removed: '${task}'`);
}

function searchTasks(tasks, keyword) {
    let kw = keyword.toLowerCase();
    return tasks.filter(t => t.toLowerCase().includes(kw));
}

function showTasks(tasks) {
    if (tasks.length === 0) { console.log("No tasks!"); return; }
    tasks.forEach((task, i) => console.log(`  ${i + 1}. ${task}`));
}

let todo = [];
addTask(todo, "Buy groceries");
addTask(todo, "Finish JS lesson");
addTask(todo, "Buy birthday gift");
addTask(todo, "Buy groceries");  // Duplicate!

showTasks(todo);
console.log("Search 'buy':", searchTasks(todo, "buy"));
removeTask(todo, "Finish JS lesson");
showTasks(todo);
static void AddTask(List<string> tasks, string task)
{
    if (tasks.Contains(task))
    {
        Console.WriteLine($"'{task}' already exists!");
        return;
    }
    tasks.Add(task);
    Console.WriteLine($"Added: '{task}'");
}

static void RemoveTask(List<string> tasks, string task)
{
    if (!tasks.Remove(task))  // Remove returns false if not found
    {
        Console.WriteLine($"'{task}' not found!");
        return;
    }
    Console.WriteLine($"Removed: '{task}'");
}

static List<string> SearchTasks(List<string> tasks, string keyword)
{
    string kw = keyword.ToLower();
    return tasks.Where(t => t.ToLower().Contains(kw)).ToList();
}

static void ShowTasks(List<string> tasks)
{
    if (tasks.Count == 0) { Console.WriteLine("No tasks!"); return; }
    for (int i = 0; i < tasks.Count; i++)
        Console.WriteLine($"  {i + 1}. {tasks[i]}");
}

// Test
List<string> todo = new List<string>();
AddTask(todo, "Buy groceries");
AddTask(todo, "Finish C# lesson");
AddTask(todo, "Buy birthday gift");
ShowTasks(todo);

var results = SearchTasks(todo, "buy");
Console.WriteLine($"Search 'buy': {string.Join(", ", results)}");
RemoveTask(todo, "Finish C# lesson");
ShowTasks(todo);
🎓 Instructor Note: Delivery Guidance

Exercise 1 is a warm-up to practice basic CRUD operations. Exercise 2 introduces aggregation — it's a natural way to combine what students learned about loops (Module 3) with lists. Note how Python's built-in max/min/sum, JS's Math.max(...spread) + .reduce(), and C#'s LINQ methods each handle statistics differently. Exercise 3 is the capstone — a realistic mini-project that combines add/remove/search/display. Challenge fast students to add a "mark complete" feature or priority levels.

Summary

🎉 Key Takeaways

  • Lists/arrays are ordered collections accessed by zero-based index
  • Negative indexing counts from the end: Python [-1], JS .at(-1), C# [^1]
  • Adding: Python .append(), JS .push(), C# .Add()
  • Removing: Python .remove()/.pop(), JS .splice()/.pop(), C# .Remove()/.RemoveAt()
  • Slicing creates copies: Python [1:4], JS .slice(1,4), C# [1..4]
  • JavaScript's .sort() needs a comparator for numbers — .sort((a,b) => a-b)
  • C# has fixed-size arrays and resizable List<T> — prefer List<T> for most tasks
  • Python list comprehensions = JS .map()/.filter() = C# LINQ .Select()/.Where()

🚀 What's Next?

Lists store values by position. But what if you want to look things up by name? Next up: dictionaries, objects, and maps — collections that store key-value pairs for lightning-fast lookups.

🎯 Quick Check

Question 1: What is the index of the first element in a list/array?

Question 2: In JavaScript, what's the difference between .slice() and .splice()?

Question 3: What does [n * 2 for n in [1, 2, 3]] produce in Python?