Lists & Indexing Operations
Master Python's dynamic arrays under the hood: contiguous 64-bit pointer blocks, amortized O(1) over-allocation, zero-based and negative indexing, sequence slicing, and memory aliasing defense.
01 / Concept Foundations: Dynamic Arrays & Pointer Memory
Mental Model: The Dynamic Address Ledger
Imagine an address ledger where each row holds a postal delivery slip. The ledger itself does not hold the actual heavy parcels; it holds ordered, 8-byte slip addresses pointing to where the parcels are stored across the warehouse. When you insert or remove an address slip, the items themselves never move in memory—only the address slips are shifted.
In Python, a list is an ordered, mutable sequence of arbitrary object references. Unlike static primitive arrays in C or Java—which require a fixed capacity and homogeneous data types—Python lists can dynamically grow or shrink at runtime and hold any mix of data types: integers, strings, floats, booleans, and nested collections.
Under CPython, a list is represented by the C struct PyListObject:
- ob_refcnt & ob_type: Standard PyObject header tracking reference count and Python type metadata.
- ob_size: Current logical item count accessible in O(1) time via
len(lst). - ob_item: Double pointer (
PyObject**) addressing a contiguous memory block of 64-bit pointers. - allocated: Total physical capacity currently reserved in memory.
Algorithmic Time Complexities
| Operation | CPython Time | Memory Impact |
|---|---|---|
| lst[i] / lst[i] = x | O(1) | Direct pointer dereference |
| lst.append(x) | Amortized O(1) | Over-allocated growth formula |
| lst.pop() | O(1) | Decrements ob_size |
| lst.pop(0) / del lst[0] | O(n) | Shifts trailing N-1 pointers via memmove |
| lst.insert(0, x) | O(n) | Shifts all N pointers rightward |
| x in lst / lst.index(x) | O(n) | Linear pointer scan |
Architecture Comparison: Fixed Homogeneous Array vs. Python Dynamic Pointer List
// C: Contiguous inline integers (fixed 16 bytes total)
int numbers[4] = {10, 20, 30, 40};
// Indexing: address = base + (index * sizeof(int))
// Cannot append; resizing requires malloc() + memcpy()# Python: PyListObject containing contiguous PyObject* pointers items = [10, "hello", 3.14, True] # Indexing: *(ob_item + i) -> dereferences heap PyObject items.append(99) # Amortized O(1) over-allocation dynamic resize
02 / Syntax Anatomy: Slice Assignment & In-Place Mutation
Click any component of the slice assignment statement below to inspect how Python modifies sequences in-place without generating a new container.
Click any syntax token above to inspect CPython memory mechanics and in-place replacement behavior.
03 / Execution Architecture: CPython Dynamic Over-Allocation Engine
How does lst.append() achieve amortized O(1) performance? Step through CPython's exponential dynamic memory growth sequence.
Logical size is 0 (ob_size = 0), and physical capacity is 0 (allocated = 0). The pointer ob_item is NULL. No heap memory is reserved yet for pointer elements.
PyListObject (lst): ob_refcnt: 1 ob_size: 0 (len = 0) allocated: 0 (capacity = 0) ob_item: NULL
04 / Sequence Comparison Matrix: Architecture & Trade-Offs
| Dimension | Python List | C / NumPy Array |
|---|---|---|
| Element Types | Heterogeneous (any Python objects) | Strictly homogeneous (int32, float64, etc.) |
| Memory Layout | Contiguous array of 64-bit pointers | Contiguous raw binary bytes inline |
| Resizing | Dynamic, amortized O(1) append | Fixed capacity (reallocation creates copy) |
| Cache Locality | Pointer chasing can miss L1/L2 data cache | Maximum SIMD & CPU cache prefetch efficiency |
| Best For | General app state, queues, stacks, JSON models | High-throughput numerical matrix mathematics |
05 / Syntax Sandbox & Sequence Snippets
Explore the 12 essential Python list manipulation patterns. Copy or run any snippet directly in the playground.
empty = [] # Literal empty list numbers = [1, 2, 3, 4] # Homogeneous list mixed = ["Alice", 30, 98.6, True] # Heterogeneous list zeros = [0] * 5 # Multiplication: [0, 0, 0, 0, 0] from_range = list(range(1, 6)) # Constructor from iterable: [1, 2, 3, 4, 5]
06 / Tiered Walkthrough Examples: Enterprise Production Logic
E-Commerce Shopping Cart & Order Audit Engine
Problem Specification
Construct an e-commerce shopping cart management module. The system starts with an initial list of cart items and corresponding price tags. A customer requests to remove an item by name ('mouse'), add an item ('webcam', price 59.99), compute the subtotal, calculate 8% sales tax (rounded to 2 decimal places), and print an itemized checkout receipt.
Production Implementation
def process_shopping_cart():
cart_items = ["laptop", "mouse", "keyboard", "monitor"]
item_prices = [999.99, 29.99, 79.99, 249.99]
tax_rate = 0.08
print("=" * 44)
print(f"{'PYLABS E-COMMERCE CART AUDITOR':^44}")
print("=" * 44)
# 1. Remove 'mouse' if present
item_to_remove = "mouse"
if item_to_remove in cart_items:
remove_idx = cart_items.index(item_to_remove)
removed_product = cart_items.pop(remove_idx)
removed_price = item_prices.pop(remove_idx)
print(f"[REMOVED] {removed_product} (-${removed_price:.2f})")
# 2. Add 'webcam' with price $59.99
cart_items.append("webcam")
item_prices.append(59.99)
print(f"[ADDED] webcam (+${59.99:.2f})")
print("-" * 44)
print("Itemized Cart:")
for idx in range(len(cart_items)):
print(f" {idx + 1}. {cart_items[idx]:<20} ${item_prices[idx]:>8.2f}")
# 3. Financial calculations
subtotal = sum(item_prices)
tax_amount = round(subtotal * tax_rate, 2)
final_total = round(subtotal + tax_amount, 2)
print("-" * 44)
print(f"Subtotal ({len(cart_items)} items):{'':<10} ${subtotal:>8.2f}")
print(f"Estimated Tax (8%):{'':<13} ${tax_amount:>8.2f}")
print(f"Final Order Total:{'':<14} ${final_total:>8.2f}")
print("=" * 44)
return cart_items, final_total
items, total = process_shopping_cart()
Engineering Deep Dive
This beginner example demonstrates fundamental list manipulation techniques in a business domain. Two parallel lists (`cart_items` and `item_prices`) maintain synchronized state. First, we verify item presence using `item_to_remove in cart_items` to avoid raising a fatal `ValueError`. We locate its offset using `.index()`, then simultaneously remove the product and its price using `.pop(remove_idx)`, which returns the extracted elements for reporting. Next, `.append()` adds the new item and its price to the end in amortized $O(1)$ time. Built-in `sum()` aggregates all floating-point prices, and standard formatting (`:.2f`) produces a clean, professional financial breakdown.
Cryptocurrency Order Book Depth & Matching Engine
Problem Specification
Implement a high-frequency cryptocurrency order book manager. Bids (buy orders) are sorted descending, while asks (sell orders) are sorted ascending. The engine must support inserting new limit orders, matching crossed orders at the top of the book, returning independent depth snapshots to avoid state pollution, and computing aggregate volume depth.
Production Implementation
class CryptoOrderBook:
def __init__(self, symbol: str):
self.symbol = symbol
# Bids: sorted descending (highest buyer first)
self.bids = [102.50, 101.75, 101.00]
# Asks: sorted ascending (lowest seller first)
self.asks = [103.00, 103.50, 104.25]
def add_bid(self, price: float):
self.bids.append(price)
self.bids.sort(reverse=True)
def add_ask(self, price: float):
self.asks.append(price)
self.asks.sort()
def match_orders(self) -> list[dict]:
trades = []
# While best bid >= best ask, an execution occurs
while self.bids and self.asks and self.bids[0] >= self.asks[0]:
best_bid = self.bids.pop(0)
best_ask = self.asks.pop(0)
exec_price = round((best_bid + best_ask) / 2, 2)
trades.append({"bid": best_bid, "ask": best_ask, "cleared": exec_price})
return trades
def get_market_snapshot(self) -> dict:
# CRITICAL: Return shallow copies so external callers cannot mutate engine state
return {
"symbol": self.symbol,
"bids": self.bids.copy(),
"asks": self.asks.copy(),
"spread": round(self.asks[0] - self.bids[0], 2) if self.bids and self.asks else 0.0
}
# Instantiate order book
book = CryptoOrderBook("BTC/USD")
print(f"Initial Market Spread: ${book.get_market_snapshot()['spread']:.2f}")
# Incoming aggressive buyer places bid at $103.25 (crosses spread!)
print("\n[ORDER] Placing aggressive bid at $103.25...")
book.add_bid(103.25)
# Execute matching engine
executed_trades = book.match_orders()
for t in executed_trades:
print(f"[TRADE EXECUTED] Matched Bid ${t['bid']:.2f} with Ask ${t['ask']:.2f} -> Cleared at ${t['cleared']:.2f}")
# Verify snapshot isolation
snapshot = book.get_market_snapshot()
print("\nPost-Trade Order Book Snapshot:")
print(f" Top Bids: {snapshot['bids']}")
print(f" Top Asks: {snapshot['asks']}")
print(f" New Spread: ${snapshot['spread']:.2f}")
# Ensure snapshot tampering does not affect live book
snapshot['bids'].append(999.99)
assert 999.99 not in book.bids, "CRITICAL: Live order book corrupted by external snapshot reference!"
print("\nSnapshot isolation verified: External mutation did not affect internal book state.")
Engineering Deep Dive
In quantitative finance and distributed trading platforms, order books maintain sorted sequence lists representing market depth. This example showcases both sequence mutation and defensive programming. Bids and asks are kept sorted via `.sort(reverse=True)` and `.sort()`. When an aggressive order arrives that crosses the bid-ask spread (`best_bid >= best_ask`), `.pop(0)` removes the matched liquidity from the top of both lists. Most importantly, `get_market_snapshot()` returns `self.bids.copy()`. If it had returned `self.bids` directly, any API client modifying the returned dictionary would silently alter the real-time order book, causing phantom trades and financial loss. Defensive shallow copying guarantees internal state integrity.
Sliding-Window Ring Buffer Telemetry Aggregator
Problem Specification
Build an in-memory sliding-window telemetry analyzer for high-throughput server metrics. The ring buffer has a fixed capacity K. As new CPU utilization float samples arrive, the oldest sample is evicted once capacity is reached. The component must compute min, max, average, and exact median across the current window, provide a decoupled history snapshot, and execute without third-party mathematical dependencies.
Production Implementation
class TelemetryRingBuffer:
"""
Fixed-capacity sliding window metric buffer maintaining recent K observations.
Provides O(1) append/evict and compute metrics without memory leaks.
"""
def __init__(self, capacity: int):
if capacity <= 0:
raise ValueError("Buffer capacity must be a positive integer")
self.capacity = capacity
self._samples = []
def record(self, sample: float):
# Maintain fixed window capacity: evict oldest if at limit
if len(self._samples) >= self.capacity:
self._samples.pop(0) # Evict oldest element
self._samples.append(float(sample))
def get_summary(self) -> dict:
if not self._samples:
return {"count": 0, "min": 0.0, "max": 0.0, "avg": 0.0, "median": 0.0, "latest": 0.0}
n = len(self._samples)
min_val = min(self._samples)
max_val = max(self._samples)
avg_val = round(sum(self._samples) / n, 2)
# Compute median via sorted copy (do NOT mutate internal buffer!)
sorted_samples = sorted(self._samples)
if n % 2 == 1:
median_val = sorted_samples[n // 2]
else:
mid1 = sorted_samples[(n // 2) - 1]
mid2 = sorted_samples[n // 2]
median_val = (mid1 + mid2) / 2.0
return {
"count": n,
"min": round(min_val, 2),
"max": round(max_val, 2),
"avg": avg_val,
"median": round(median_val, 2),
"latest": round(self._samples[-1], 2)
}
def export_window(self) -> list[float]:
# Slicing [:] creates an independent shallow copy of the window
return self._samples[:]
# Simulation: Stream 6 server CPU telemetry samples into a capacity-4 buffer
buffer = TelemetryRingBuffer(capacity=4)
simulated_stream = [45.0, 52.0, 48.0, 60.0, 55.0, 72.0]
print("=" * 56)
print(f"{'STREAMING TELEMETRY RING BUFFER (CAPACITY: 4)':^56}")
print("=" * 56)
for i, reading in enumerate(simulated_stream, 1):
buffer.record(reading)
summary = buffer.get_summary()
current_window = buffer.export_window()
print(f"Sample #{i}: {reading:>5.1f}% | Window: {current_window}")
print(f" -> Min: {summary['min']}% | Max: {summary['max']}% | Avg: {summary['avg']}% | Median: {summary['median']}%")
print("=" * 56)
Engineering Deep Dive
This advanced example illustrates a core pattern in observability agents (like Prometheus Node Exporter or Datadog agents): maintaining bounded in-memory sliding windows. Notice how the window evolves: on sample #5 (55.0%), the capacity of 4 is exceeded, so `.pop(0)` evicts the oldest reading (45.0%), sliding the active window forward to `[52.0, 48.0, 60.0, 55.0]`. Crucially, when computing the median, we call `sorted(self._samples)` rather than `self._samples.sort()`. Calling `self._samples.sort()` would permanently reorder the ring buffer, destroying the chronological sequence necessary for FIFO eviction! Finally, `export_window()` uses slice notation `[:]` to return a copy, preventing caller side-effects.
07 / Anti-Patterns & Memory Pitfalls
Careless sequence manipulation is a leading source of insidious shared-state bugs and accidental O(n²) bottlenecks. Study these 6 battle-tested anti-patterns.
🟢 Beginner Pitfall: The Reference Aliasing Trap (b = a)
Symptom: Modifying backup silently mutates the original users list, leading to insidious shared-state bugs.
users = ["alice", "bob"]
backup = users # BUG: creates an alias, NOT a copy!
backup.append("charlie")
print(users) # Expected: ['alice', 'bob'], Got: ['alice', 'bob', 'charlie']users = ["alice", "bob"]
backup = users.copy() # or list(users) or users[:]
backup.append("charlie")Why it happens: Assignment (b = a) binds a second variable name to the identical PyListObject in memory without copying any data.
🟢 Beginner Pitfall: Modifying a List While Iterating Over It
Symptom: Elements are skipped because the internal iterator index increments while elements shift leftward.
numbers = [1, 2, 3, 4, 5, 6]
for n in numbers:
if n % 2 == 0:
numbers.remove(n) # BUG: Modifies list during loop!
print(numbers) # Expected: [1, 3, 5], Got: [1, 3, 5, 6] (skips 4!)# Iterate over a shallow copy:
for n in numbers[:]:
if n % 2 == 0:
numbers.remove(n)
# Or use a list comprehension:
numbers = [n for n in numbers if n % 2 != 0]Why it happens: When index 1 ('2') is removed, '3' shifts to index 1 and '4' shifts to index 2. The iterator advances to index 2, skipping 4 entirely.
🟡 Intermediate Pitfall: The Multiplied Mutable Sublist Trap ([[0]*3]*3)
Symptom: Mutating a single cell in the first row inadvertently modifies that column in EVERY row simultaneously.
grid = [[0] * 3] * 3 # BUG: 3 references to the SAME inner list!
grid[0][0] = 1
print(grid) # Expected: [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
# Actual: [[1, 0, 0], [1, 0, 0], [1, 0, 0]]# Use a list comprehension to create distinct inner list objects: grid = [[0] * 3 for _ in range(3)] grid[0][0] = 1
Why it happens: The * operator copies the reference to the single inner list [0, 0, 0] three times, not 3 independent lists.
🟡 Intermediate Pitfall: Confusing .append() with .extend()
Symptom: Produces unwanted nested list structure instead of flattening elements into the existing list.
items = [1, 2] items.append([3, 4]) # BUG: Appends the sublist as a single nested element! print(items) # [1, 2, [3, 4]] instead of [1, 2, 3, 4]
items = [1, 2] items.extend([3, 4]) # Result: [1, 2, 3, 4]
Why it happens: append() treats its argument as a single object, whereas extend() iterates through the argument and appends each element individually.
🔴 Advanced Pitfall: Assigning the Result of list.sort() (lst = lst.sort())
Symptom: The variable becomes None, destroying the list and causing subsequent AttributeError exceptions.
scores = [45, 12, 89] scores = scores.sort() # BUG: .sort() returns None! print(scores) # None (original list destroyed!)
scores = [45, 12, 89] scores.sort() # Modifies in place; do not assign! # Or if you want a new sorted list: sorted_scores = sorted(scores)
Why it happens: Python convention dictates that in-place mutating methods return None to clearly signal that no new object was allocated.
🔴 Advanced Pitfall: Accidental O(n^2) Performance from Repeated pop(0) or insert(0)
Symptom: Script execution grinds to a halt, taking minutes to drain large collections.
queue = list(range(100000))
processed = []
while queue:
processed.append(queue.pop(0)) # BUG: O(n) memmove on every single item!from collections import deque
queue = deque(range(100000))
processed = []
while queue:
processed.append(queue.popleft()) # O(1) double-ended queue operationWhy it happens: pop(0) shifts all remaining N-1 pointers left in memory. Repeating this N times results in O(N^2) total operations.
08 / Real-World Industry Systems
See how leading technology organizations leverage Python list mechanics in distributed web proxies, ML pipelines, cloud runners, and core banking engines.
🟢 Simple Script: Sliding-Window Rate Limiter Timestamp Eviction
API gateway proxies (like Envoy or Cloudflare Workers) track per-IP client request timestamps in a list to enforce rate limits (e.g., maximum 100 requests per 60-second sliding window).
import time
class SlidingWindowRateLimiter:
def __init__(self, max_requests: int, window_seconds: float):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.timestamps = []
def is_allowed(self, current_time: float) -> bool:
cutoff = current_time - self.window_seconds
# Evict timestamps older than sliding window threshold
while self.timestamps and self.timestamps[0] <= cutoff:
self.timestamps.pop(0)
if len(self.timestamps) < self.max_requests:
self.timestamps.append(current_time)
return True
return False
limiter = SlidingWindowRateLimiter(max_requests=3, window_seconds=10.0)
req_times = [1.0, 3.0, 5.0, 8.0, 12.0]
for t in req_times:
allowed = limiter.is_allowed(t)
print(f"Timestamp {t:>4.1f}s -> Allowed: {allowed:<5} | Active Window: {limiter.timestamps}")
At timestamp 8.0s, the client has already used 3 requests within the last 10s (1.0, 3.0, 5.0), so request 8.0s is rejected. At timestamp 12.0s, the cutoff is 2.0s, so the 1.0s timestamp is popped, allowing the new request.
🟡 Scaled Service: K-Fold Cross-Validation Train/Test Split Slicer
Machine learning pipelines partition dataset index lists into K folds, slicing out validation folds while combining remaining folds for training without data leakage.
def generate_k_folds(dataset_size: int, k: int = 4):
indices = list(range(dataset_size))
fold_size = dataset_size // k
folds = []
for i in range(k):
val_start = i * fold_size
val_end = val_start + fold_size if i != k - 1 else dataset_size
# Slicing creates independent train and validation splits
val_indices = indices[val_start:val_end]
train_indices = indices[:val_start] + indices[val_end:]
folds.append((train_indices, val_indices))
return folds
k_splits = generate_k_folds(dataset_size=10, k=3)
for fold_idx, (train, val) in enumerate(k_splits, 1):
print(f"Fold {fold_idx}: Train {train} | Validation {val}")
List slicing (`indices[val_start:val_end]`) and concatenation (`+`) partition the dataset cleanly, ensuring that training and validation subsets remain disjoint.
🔴 Enterprise Production: CI/CD Build Pipeline Step Dependency Stack
Automated deployment runners (like GitHub Actions or GitLab CI) push pipeline build stages onto an execution stack and pop them during rollbacks on failure.
deployment_steps = ["lint", "test", "build_docker", "deploy_staging"]
executed_stack = []
print("Starting Deployment Pipeline:")
for step in deployment_steps:
print(f" Executing step: {step}...")
executed_stack.append(step)
if step == "build_docker":
print(" [ERROR] Docker build failed: Disk quota exceeded!")
break
print("\nInitiating Automated Rollback Sequence:")
while executed_stack:
failed_step = executed_stack.pop() # Roll back in LIFO order
print(f" Reverting stage: {failed_step}")
print("Rollback completed safely.")
Using `.append()` to push execution steps and `.pop()` to unwind them ensures strict Last-In-First-Out (LIFO) reversal of environmental mutations during infrastructure rollbacks.
🔴 Enterprise Production: Banking Ledger Double-Entry Transaction Reconciler
Core banking engines audit transaction ledgers, filtering debits and credits and verifying account balance equilibrium.
transactions = [
("DEPOSIT", 500.00),
("WITHDRAWAL", 120.00),
("DEPOSIT", 250.00),
("WITHDRAWAL", 45.50)
]
balance = 0.0
audit_log = []
for action, amount in transactions:
if action == "DEPOSIT":
balance += amount
audit_log.append(f"+${amount:.2f} (Balance: ${balance:.2f})")
elif action == "WITHDRAWAL":
balance -= amount
audit_log.append(f"-${amount:.2f} (Balance: ${balance:.2f})")
print(f"Ledger Audited ({len(audit_log)} entries):")
for entry in audit_log:
print(f" {entry}")
print(f"Final Verified Ledger Balance: ${balance:.2f}")
Dynamic lists allow sequential streaming of financial events, maintaining an append-only audit trail essential for regulatory compliance.
09 / Deep Dive Case Study: Netflix
Netflix's home page recommendation engine generates personalized rows ('Because you watched...', 'Top 10 Today') composed of video entities. Microservices ingest candidate recommendation lists from machine learning ranking models. In memory, candidate video lists undergo fast sequence operations: 1. **Deduplication and Filtering:** Titles already watched are filtered out. 2. **Shelf Chunking via Slicing:** The ranked candidate pool is sliced into device-specific display batches (e.g., `candidates[:10]` for mobile screens, `candidates[:25]` for 4K Smart TVs). 3. **Defensive Shallow Copying:** Shallow copies of recommendation shelves are isolated before applying localized A/B testing permutations (such as personalized thumbnail artwork selection). Because CPython lists store contiguous arrays of 64-bit pointers, sequential traversal and slicing of video IDs leverage modern CPU L1/L2 data cache prefetching, achieving sub-millisecond layout generation before serialization to gRPC/JSON.
Shelf Generation & Viewport Slicing Pattern
def render_subscriber_shelf(ranked_candidates: list[str], watched_history: list[str], screen_limit: int) -> list[str]:
# 1. Deduplicate candidates against watched history
fresh_candidates = []
for movie_id in ranked_candidates:
if movie_id not in watched_history:
fresh_candidates.append(movie_id)
# 2. Slice to device viewport limit
shelf = fresh_candidates[:screen_limit]
# 3. Defensive copy for client metadata enrichment
return shelf.copy()
all_movies = ["m_101", "m_102", "m_103", "m_104", "m_105", "m_106"]
user_history = ["m_102", "m_105"]
rendered = render_subscriber_shelf(all_movies, user_history, screen_limit=3)
print("Personalized Home Shelf:", rendered)
Architectural Takeaways
- Contiguous pointer arrays in Python lists maximize CPU cache locality during sequence iterations.
- Slicing creates clean viewport windows without risking out-of-bounds IndexError crashes.
- Defensive copying prevents experimental A/B test variations from corrupting upstream recommendation models.
10 / Guided Exercises
Step-by-step programming tasks to cement list slicing, front eviction, and 2D matrix transformations.
Dynamic Task Queue Processor
Append 'task_email' and 'task_logs' to queue. Then pop the first two items from index 0 of queue and append each to processed. Print both lists.
# Initial task queue
queue = ["task_auth", "task_db"]
processed = []
# 1. Append 'task_email' and 'task_logs' to queue
# 2. Process (pop from front, index 0) the first 2 tasks and append them to processed
print("Processed:", processed)
print("Queue:", queue)
- Use queue.append('task_email') and queue.append('task_logs').
- Use queue.pop(0) to remove the front item, and processed.append(...) to save it.
- Repeat pop(0) twice.
Stock Price Ticker Slicer & Statistics
Slice weekly_prices from index 1 to 6 to capture weekday trading days into weekday_prices. Find min, max, and avg (sum/len rounded to 2 decimals). Create rev_prices using slicing [::-1].
# 7 days of price observations (index 0 is Sunday, index 6 is Saturday)
weekly_prices = [142.5, 145.0, 144.2, 146.8, 148.0, 147.5, 149.0]
# 1. Extract weekday prices (Monday to Friday, indices 1 to 5 inclusive) using a slice into weekday_prices
# 2. Compute low_price (min), high_price (max), and avg_price (sum / length rounded to 2 decimals)
# 3. Create rev_prices as a reversed copy of weekday_prices using slice step -1
print("Weekday prices:", weekday_prices)
print(f"Low: {low_price} | High: {high_price} | Avg: {avg_price:.2f}")
print("Reversed:", rev_prices)
- weekday_prices = weekly_prices[1:6]
- low_price = min(weekday_prices); high_price = max(weekday_prices)
- avg_price = round(sum(weekday_prices) / len(weekday_prices), 2)
- rev_prices = weekday_prices[::-1]
Matrix Column Extraction & Row Sums
Iterate over matrix to collect index 1 from each row into col1. Append [100, 110, 120] to matrix. Compute the sum of each row into row_sums. Print both results.
matrix = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
]
# 1. Extract column 1 (the middle element of each row) into col1
col1 = []
# 2. Append a new row [100, 110, 120] to matrix
# 3. Compute row_sums as a list containing the sum of each row in matrix
row_sums = []
print("Column 1:", col1)
print("Row sums:", row_sums)
- for row in matrix: col1.append(row[1])
- matrix.append([100, 110, 120])
- for row in matrix: row_sums.append(sum(row))
11 / Hands-On Practice Tasks
Autonomous implementation challenges with unit assertions. Solve these in your local IDE or PyLabs playground.
E-Commerce Discount & Invoice Generator
Create a shopping cart price processing pipeline. Given items = [('Laptop', 1200.0), ('Book', 25.0), ('Monitor', 300.0), ('Pen', 4.0)], apply a 10% discount to any item whose price exceeds $50.0. Calculate the discounted subtotal, add 8% sales tax, and print an itemized invoice.
- Input items: [('Laptop', 1200.0), ('Book', 25.0), ('Monitor', 300.0), ('Pen', 4.0)].
- Apply 10% discount (price * 0.9) to items with price > 50.0.
- Store discounted tuples in a list named discounted.
- Calculate subtotal = sum of discounted prices, tax = round(subtotal * 0.08, 2), final_total = round(subtotal + tax, 2).
- Print subtotal and final total.
items = [("Laptop", 1200.0), ("Book", 25.0), ("Monitor", 300.0), ("Pen", 4.0)]
discounted = []
# Implement discounting and financial totals
Least Recently Used (LRU) Cache Key Order Tracker
Simulate an LRU cache eviction order using a list. The cache has a capacity of 3 items. As keys are accessed, if the key is already in the list, move it to the end (most recently used). If the key is new and the cache is full, evict the least recently used key (index 0) before appending the new key.
- Capacity = 3, operations = ['user_1', 'user_2', 'user_3', 'user_1', 'user_4'].
- If key in cache_keys: remove it from current position and append to end.
- Else if len(cache_keys) >= capacity: pop(0) to evict oldest, then append new key.
- Print final state of cache_keys (expected: ['user_3', 'user_1', 'user_4']).
capacity = 3 operations = ["user_1", "user_2", "user_3", "user_1", "user_4"] cache_keys = [] # Simulate LRU eviction order
12 / Key Takeaways & Core Invariants
Lists are ordered, mutable, heterogeneous collections implemented in CPython as contiguous dynamic arrays of 64-bit object pointers.
Access elements using 0-based positive indexing or negative indexing (-1 for the last item). Indexing out-of-bounds raises IndexError.
Slice sequences using [start:stop:step]; slicing never raises IndexError and clamps gracefully to list boundaries.
.append() adds a single item in amortized O(1) time; .insert(0, x) and .pop(0) shift all pointers in O(n) time.
Variable assignment (b = a) creates an alias, not a copy. Use .copy(), list(), or [:] for shallow copies, and copy.deepcopy() for nested structures.
In-place mutating methods like .sort() and .reverse() modify the list directly and return None. Do not assign their return value back to the variable.
Never initialize 2D grids using [[0]*cols]*rows because each row references the exact same list in memory. Use list comprehensions instead.
13 / Knowledge Check: Quiz Booster
Validate your understanding of pointer aliasing, slice stepping, dynamic resizing, and in-place sorting return values.
nums = [0, 1, 2, 3, 4, 5, 6] print(nums[1:6:2])
a = [1, 2] b = a b.append(3) print(a)
grid = [[0] * 2] * 2 grid[0][0] = 7 print(grid)
letters = ['a', 'b', 'c', 'd'] letters[1:3] = [9, 8] print(letters)
x = [1, 2] x.extend([3, 4]) x.append([5, 6]) print(x)
14 / Recommended Searches, Cheat Sheet & FAQ
Curated Google Searches & Deep Dive References
Inspect official CPython source code implementing dynamic exponential array growth.
Diagrams illustrating reference sharing vs recursive object cloning in memory.
Understand how slice assignments resize lists in-place without memory leaks.
Empirical latency graphs showing O(1) deque evictions vs O(n) list.pop(0).
Hardware performance analysis comparing contiguous pointers to SIMD NumPy arrays.
Read Python's design philosophy on in-place mutators versus pure functions.
Standard Library Sequence Reference Cards
b = a.copy()Returns a brand-new PyListObject containing identical object references. Equivalent to a[:].
lst.append(x)Amortized O(1) pointer append to the end. Expands capacity using over-allocation formula.
item = lst.pop([idx])Removes and returns item at index. Default is -1 (O(1)). Index 0 shifts all pointers (O(n)).
Frequently Asked Questions
This is a deliberate Python design convention: methods that mutate an object in place return None to signal that the operation modified the existing object rather than allocating a new one. If you want a new sorted list, use the built-in sorted(lst) function.
Lists & Indexing Operations practice
Create numbers = [4, 1, 3]. Append 2, sort the list in place, then print it.
Expected output: [1, 2, 3, 4]
Coding Practice is Locked
Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.
Practice quiz
Check yourself
15 questions with explanations after submission, so you can correct course as you go.
Lists & Indexing Operations quiz
LockedYou must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.
Lesson incomplete
Not completedCreate an ID or sign in to save lesson completion across devices.