📋 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>— preferList<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?