๐ Day 09 : Loops
๐ฏ Enterprise Objective
Iteration is the engine of data processing. Today we graduate from basic loops to professional iteration patterns. You will learn to write memory-efficient pipelines using generators and the itertools module, allowing you to process gigabytes of data seamlessly without crashing your system.
๐ Strategic Overview
| # | Topic | Key Concept | Core Use Case |
|---|---|---|---|
| 1 | for loops | Iterables & Iterators | Standard data traversal |
| 2 | while loops | Condition-based | Polling & Pagination |
| 3 | Loop Control | continue, for...else | Filtering & Search logic |
| 4 | Patterns | enumerate(), zip() | Multi-list processing |
| 5 | Generators | yield | O(1) Memory pipelines |
| 6 | Expressions | (expr for x in data) | Lazy reductions |
| 7 | Itertools | chain, combinations | Combinatorial math |
1. For Loops & Iterables : Data Traversal
A for loop in Python iterates over an iterable (like a list, string, or dictionary). Unlike C-style loops that use index counters, Python's for loop extracts the actual items directly. Under the hood, it calls iter() to get an iterator, and next() until it hits a StopIteration error.
| Iterable Type | Iteration Yields | Example |
|---|---|---|
| List / Tuple | Items | for item in [1, 2]: |
| String | Characters | for char in 'abc': |
| Dictionary | Keys | for key in {'a': 1}: |
| Set | Items (Unordered) | for item in {1, 2}: |
| File object | Lines | for line in open('f.txt'): |
๐ผ Why Data Analysts Care
โข Data Processing: Iterating over rows in a dataset or JSON objects in an API response
โข File Parsing: Reading gigabytes of log files line-by-line without loading them into memory
โ ๏ธ Modifying While Iterating
Never modify a list (adding/removing items) while iterating over it with a for loop. It will skip elements or crash. Iterate over a copy instead: for item in my_list.copy():.
๐งช Concept Checks: For Loops
Q1. Write a for loop that iterates over data = [10, 20, 30] and prints the square of each number.
Q2. Iterate over the string "Python" and print each character multiplied by 3 (e.g., "PPP").
Q3. Create a dictionary d = {"a": 1, "b": 2}. Use a standard for loop (which yields keys) to print both key and value: d[key].
Q4. Demonstrate the modification trap: iterate over lst = [1, 2, 3, 4], and if the item is even, remove it using lst.remove(). Print the list. Did it work correctly? Explain.
Q5. Fix the previous code by iterating over a slice copy lst[:]. Print the corrected list.
2. While Loops & State Machines : Condition-Based Iteration
A while loop runs as long as a condition evaluates to True. It is used when the number of iterations is unknown beforehand (e.g., waiting for an API response, polling a database, or reading user input until they type 'quit').
# Polling pattern
while not db.is_ready():
time.sleep(1)
print("Waiting...")
๐ผ Why Data Analysts Care
โข API Pagination: Fetching pages of data until next_page_token is null
โข Retry Logic: Attempting to connect to a server up to 5 times before failing
โข Simulation: Running a simulation until a specific convergence threshold is met
โ ๏ธ Infinite Loops
Always ensure the condition inside a while loop will eventually become False, or provide a break statement. Otherwise, your program will freeze forever.
๐งช Concept Checks: While Loops
Q1. Write a while loop that prints powers of 2 (1, 2, 4, 8...) as long as the value is less than 100.
Q2. Write a while loop to find the first number divisible by both 7 and 13, starting your search at n = 1. Print it.
Q3. Create a list items = ["a", "b", "c"]. Use a while items: loop and .pop() to empty the list, printing each item.
Q4. Write a simulation: start with money = 100. In a while loop, subtract a random amount between 10-20 until money is < 0. Count iterations.
Q5. What is the most common cause of an infinite loop? Write a tiny script demonstrating one (and comment it out so it doesn't crash the notebook!).
3. Loop Control : Break, Continue, Pass & Else
Python provides keywords to alter loop flow. break exits the loop entirely. continue skips the rest of the current iteration and jumps to the next. Python also has a unique for...else construct where the else block runs only if the loop completes without hitting a break.
| Keyword | Effect | Common Use |
|---|---|---|
continue | Skip to next iteration | Guard clauses inside loops (filtering) |
break | Exit loop immediately | Early exit when item is found |
pass | Do nothing | Placeholder for unimplemented code |
else | Runs if NO break occurred | "Search failed" logic |
๐ผ Why Data Analysts Care
โข Data Filtering: Use continue to skip invalid rows without deeply nesting if statements
โข Search Algorithms: Use break to stop searching a massive dataset once the target is found
โข Validation: Use for...else to verify that ALL items pass a check
๐ง Pro Tip
The for...else naming is confusing. Think of it as for...no_break. It's the most Pythonic way to perform a 'search and report if not found' pattern.
๐งช Concept Checks: Loop Control
Q1. Write a loop from 1 to 10. Use continue to skip numbers divisible by 3. Print the rest.
Q2. Given data = [1, 5, -2, 8, -4], iterate and print positive numbers. Use break to stop completely if you hit a negative number.
Q3. Write a for...else loop that checks if a prime number 11 is divisible by any number from 2 to 10. If not, the else block prints "It is prime".
Q4. Write a while True: loop that increments a counter. Use an if statement and break to exit when the counter reaches 5. Print the counter.
Q5. Why is pass useful? Write an empty function or loop using pass to show how it prevents a SyntaxError.
4. Iteration Patterns : Pythonic Looping
Python emphasizes readability. Instead of manually tracking indices with range(len(data)), Python provides elegant built-in functions: enumerate() (value + index), zip() (parallel iteration), and reversed() (backwards).
| Pattern | Good Pythonic Code | Bad C-Style Code |
|---|---|---|
| Index Tracking | for i, val in enumerate(lst): | for i in range(len(lst)): val = lst[i] |
| Parallel Loops | for a, b in zip(listA, listB): | for i in range(len(listA)): a,b = listA[i], listB[i] |
| Reverse Loop | for val in reversed(lst): | for i in range(len(lst)-1, -1, -1): |
๐ผ Why Data Analysts Care
โข Feature Engineering: Combining multiple columns using zip(col1, col2)
โข Logging: Printing row numbers alongside data errors using enumerate(data, start=1)
โ ๏ธ Uneven Zip
zip() stops at the shortest iterable. If A has 5 items and B has 3, it only loops 3 times. Use itertools.zip_longest if you need to process all elements.
๐งช Concept Checks: Iteration Patterns
Q1. Given cities = ["NY", "LA", "CHI"], use enumerate(..., start=1) to print "1. NY", "2. LA", etc.
Q2. Given keys = ["a", "b", "c"] and vals = [1, 2, 3], use zip in a dictionary comprehension to map them: {k: v}. Print the dict.
Q3. What happens if you zip [1, 2, 3] with ["A", "B"]? Write the loop and print the results to see the truncation.
Q4. Loop backwards from 10 to 1 using reversed(range(1, 11)). Print the numbers.
Q5. Combine patterns: use enumerate(zip(names, scores)) to print the index, name, and score all at once. Try it!
5. Generators & yield : Lazy Memory-Efficient Evaluation
A generator is a special function that returns an iterator. Instead of computing an entire list and storing it in memory, it uses the yield keyword to produce values one at a time. It pauses its state between yields.
# Normal Function (Eats RAM)
def get_millions():
return [x for x in range(1_000_000)] # list in memory
# Generator (O(1) Memory)
def yield_millions():
for x in range(1_000_000):
yield x # yields one by one
๐ผ Why Data Analysts Care
โข Big Data Processing: Reading a 50GB CSV file line-by-line without crashing your laptop
โข Infinite Streams: Processing real-time sensor data or Kafka event streams
โข API Pagination: Yielding records page-by-page as they are requested
โ ๏ธ Generators are Single-Use
Once a generator is exhausted (loop finishes), it is empty. You cannot iterate over it a second time. You must create a new generator object.
๐งช Concept Checks: Generators
Q1. Write a generator function countdown(n) that yields numbers from n down to 1. Test it in a loop.
Q2. Demonstrate that generators are single-use: create a generator, loop over it once, then try to loop over it again. What happens?
Q3. Write a generator fibonacci(limit) that yields Fibonacci numbers up to a maximum value. Print the results.
Q4. Create a generator that yields random numbers between 1-10 infinitely. Use next() to pull 3 numbers from it manually.
Q5. Why is yield better than return when parsing a massive log file?
6. Generator Expressions : Inline Lazy Data
A generator expression is exactly like a list comprehension, but it uses parentheses () instead of square brackets []. It creates a generator object instead of a list, saving massive amounts of memory.
| Syntax | Object Created | Evaluation | Memory |
|---|---|---|---|
[x for x in data] | list | Eager (All at once) | High |
(x for x in data) | generator | Lazy (One by one) | Low O(1) |
๐ผ Why Data Analysts Care
โข Math Reductions: sum(x**2 for x in data) โ computes the sum without creating a temporary list
โข Pipelining: Chain multiple generator expressions to create a clean, memory-efficient data pipeline
๐ง Pro Tip
When passing a generator expression as the ONLY argument to a function, you can omit the extra parentheses: sum(x2 for x in range(10)) instead of sum((x2 for x in range(10))).
๐งช Concept Checks: Generator Expressions
Q1. Write a generator expression for the cubes of numbers 1-10. Assign it to gen. Print next(gen) twice.
Q2. Given data = ["10", "20", "invalid", "30"], write a generator expression that attempts to convert valid integers. Iterate and print.
Q3. Compute the sum of the first 10,000 even numbers using sum() and a generator expression. Print the result.
Q4. Use all() with a generator expression to check if all words in ["apple", "banana", "cherry"] have > 3 letters.
Q5. Measure the memory difference (sys.getsizeof()) between [x for x in range(10000)] and (x for x in range(10000)).
7. The itertools Module : Professional Iteration
The itertools module is a standard library toolkit for creating and combining iterators. It provides fast, memory-efficient tools for combinatorial math, grouping, and advanced looping patterns.
Key Itertools:
| Function | Purpose | Example |
|---|---|---|
chain(A, B) | Combine iterables sequentially | chain([1,2], [3,4]) โ 1,2,3,4 |
combinations(A, 2) | All unique pairs | (A,B), (A,C), (B,C) |
permutations(A, 2) | All ordered pairs | (A,B), (B,A), (A,C)... |
cycle(A) | Infinite loop over A | 1,2,1,2,1,2... |
groupby(A, key) | Group adjacent duplicates | Grouping sorted data |
๐ผ Why Data Analysts Care
โข Feature Interactions: Generating all pairs of columns using combinations for machine learning
โข Flattening Data: Using chain.from_iterable(list_of_lists) to flatten nested structures efficiently
โ ๏ธ Infinite Itertools
Tools like cycle, count, and repeat generate infinite streams. Always use a break condition or zip them with a finite sequence, otherwise your program will hang.
๐งช Concept Checks: Itertools
Q1. Import itertools. Use chain() to loop over [1,2,3], (4,5), and {"A", "B"} in a single for loop. Print each item.
Q2. Use itertools.combinations() to find all 3-person teams from ["Alice", "Bob", "Charlie", "David"]. Print them.
Q3. Use itertools.permutations() on [1, 2, 3] with length 2. How many results are there compared to combinations?
Q4. Write a for loop using itertools.cycle(["Red", "Green", "Blue"]). Use a manual counter to break after 7 prints.
Q5. Flatten matrix = [[1, 2], [3, 4], [5, 6]] using itertools.chain.from_iterable(). Print the resulting list.
๐ ๏ธ Professional Practice Tasks
Theory is useless without muscle memory. Complete these tasks to solidify your understanding.
Task 1 (Data Paginator): Write a generator paginate(data, page_size) that yields chunks of a list. E.g., paginate([1,2,3,4,5], 2) yields [1,2], [3,4], [5]. Test it in a loop.
Task 2 (CSV Reader Pipeline): Write two generator functions. read_lines(text) yields lines from a multi-line string. parse_csv(lines) yields lists of values. Chain them: for row in parse_csv(read_lines(data)):.
Task 3 (Prime Generator): Write a generator primes_up_to(n) that yields prime numbers. Use an internal while loop and math logic. Test with n=50.
Task 4 (For-Else Search): Given a list of dictionaries (users), write a for...else loop that searches for a user with role == 'admin'. Break and print their name if found, else print 'No admin configured'.
Task 5 (Cartesian Combinations): Given colors = ['red', 'blue'] and sizes = ['S', 'M', 'L'], use itertools.product to generate and print all possible color/size combinations.
๐ป Pure Coding Interview Questions
Q1.
Write a generator fibonacci() that yields the Fibonacci sequence infinitely. Use itertools.islice to get the first 10.
Q2.
Explain the difference between yield and return. What is a coroutine?
Q3.
Write a function flatten(nested_list) using recursion and yield from to flatten arbitrarily deep lists.
Q4.
Explain how for...else works. Write a code example showing a use case where it replaces a boolean flag.
Q5.
Write a generator expression to find the sum of all odd numbers under 1,000,000. Why is this better than a list comprehension?
Q6.
Implement a custom iterator class (with iter and next) that mimics the range(start, stop) function.
Q7.
Write a function sliding_window(iterable, n) using itertools or generators to yield n-length windows. [1,2,3,4], n=2 -> (1,2), (2,3), (3,4).
Q8.
Write a function that parses a log file line-by-line using a generator, filtering only lines containing 'ERROR'.
Q9.
Explain the StopIteration exception. Write code that manually catches it while using next().
Q10.
Write a function group_anagrams(words) using itertools.groupby. Why must the data be sorted first?
Q11.
Implement a custom zip(*iterables) function using a while loop and next().
Q12.
Write code using itertools.dropwhile and takewhile to extract a specific segment of data from a stream.
Q13.
Explain what itertools.tee does and when you would need to use it to duplicate an iterator.
Q14.
Write a function to generate all subsets (powerset) of a list using itertools.combinations.
Q15.
Write a generator chunker(iterable, size) that works on ANY iterable (not just lists) using itertools.islice.
Q16.
Implement an infinite prime number generator using a growing set of seen primes.
Q17.
Write code showing the difference between itertools.combinations and itertools.combinations_with_replacement.
Q18.
Write a function that uses a generator to find the first duplicate in a stream of data in O(1) memory.
Q19.
Explain the memory profile of any([cond(x) for x in data]) vs any(cond(x) for x in data). Why does it matter?
Q20.
Write a pipeline of three generator functions: read, filter, and transform. Connect them together.
Q21.
Implement the Collatz conjecture as a generator yielding the sequence until it reaches 1.
Q22.
Write a function that alternates yielding from two infinite generators using itertools.cycle or manual logic.
Q23.
Explain why modifying a dictionary while iterating over it raises a RuntimeError. How do you safely delete keys in a loop?
Q24.
Write a while loop that retries an API call (simulated function) with exponential backoff.
Q25.
What is yield from? Write a function that uses it to chain two separate generator functions together.
๐ Day 9 Executive Summary
| # | Topic | Key Takeaway | Professional Application |
|---|---|---|---|
| 1 | for loops | Iterates over iterables | Data traversal without indices |
| 2 | while loops | Runs while condition is true | Polling, infinite streams, pagination |
| 3 | Loop Control | break, continue, else | Fast filtering, search resolution |
| 4 | Iteration Patterns | enumerate, zip, reversed | Elegant, readable data manipulation |
| 5 | Generators | yield pauses execution | O(1) memory for massive datasets |
| 6 | Gen Expressions | (x for x in data) | Inline lazy reductions (sum/any/all) |
| 7 | Itertools | Standard library iteration tools | Combinatorics, efficient chaining |
โ Instructor's End-of-Day Checklist
โข [ ] I understand how for loops work under the hood (iter() and next()).
โข [ ] I know how to use while loops for unknown iteration counts (polling).
โข [ ] I can use continue to filter and for...else for search operations.
โข [ ] I always use enumerate() instead of range(len()).
โข [ ] I understand how yield saves memory compared to returning a full list.
โข [ ] I can write a generator expression for memory-efficient math reductions.
โข [ ] I have completed all 5 practice tasks.
โข [ ] I have reviewed all 25 interview questions.
*Next Up: Day 10 - Functions: Scope, Arguments, \args/\\kwargs, and Lambdas**