Python Loops
Note: This post is based on my old programming study notes when I taught myself.
while Loop
While loop will never stop the loop as long as the condition is True. Once the condition is False, the loop stops. If condition is always True, while loop becomes an infinite loop. To exit infinite loop in the IDE, press Ctrl+C
.
Basic Syntax
while condition:
statement_1
statement_2
...
Important Note
When using while loop, it’s necessary to define the initial state of variables used in the condition.
cookie = 0
while cookie < 10:
cookie += 1
print('He ate %d cookie' % cookie)
if cookie == 10:
print("He ate all the cookies.")
How to escape from while loop?
Use break: If while loop reaches break
, the loop will stop regardless of whether the condition is True or False.
chocolate = 10 # Initial state of chocolate variable
while True:
chocolate = chocolate - 1
print('We have %d chocolates left' % chocolate)
if chocolate == 0:
print("Chocolate Factory is shutting down. We don't have any chocolate left...")
break # Escape infinite loop
How to go back to condition of while loop?
Use continue:
num = 0 # initial number is set to zero
while num < 10:
num += 1 # num = num + 1
if num % 2 == 0:
continue
print(num)
# This will print:
# 1
# 3
# 5
# 7
# 9
Explanation:
num
starts from 0 and as long as num is below 10, the loop operates and adds 1 to num variable. When num is an even number, it goes back to the original condition (num < 10) of while loop and continues operating.
for Loop
Basic Syntax
for variable in List/Tuple/String:
statement_1
statement_2
...
Examples
# Iterating through a list
fruits = ['apple', 'banana', 'orange']
for fruit in fruits:
print(f"I like {fruit}")
# Iterating through a string
word = "Python"
for letter in word:
print(letter)
continue
in for loop
Like while loop, if for loop reaches continue
, it will go back to the original condition of for loop and keep operating from there.
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num == 3:
continue
print(num)
# Output: 1, 2, 4, 5 (skips 3)
range() Function
Syntax
range(start, end, step)
By default, if we don’t set the start
value and step
value, Python automatically considers start=0
and step=1
. range(n)
means the range is from 0 to n-1.
Examples
# Basic range
for i in range(5):
print(i) # Output: 0, 1, 2, 3, 4
# Range with start and end
for i in range(2, 8):
print(i) # Output: 2, 3, 4, 5, 6, 7
# Range with step
for i in range(0, 10, 2):
print(i) # Output: 0, 2, 4, 6, 8
Practical Example: Multiplication Table
# 9 x 9 multiplication table with for loop
for i in range(2, 10):
for j in range(1, 10):
print(i * j, end=" ")
print('') # New line after each row
# Result:
# 2 4 6 8 10 12 14 16 18
# 3 6 9 12 15 18 21 24 27
# 4 8 12 16 20 24 28 32 36
# 5 10 15 20 25 30 35 40 45
# 6 12 18 24 30 36 42 48 54
# 7 14 21 28 35 42 49 56 63
# 8 16 24 32 40 48 56 64 72
# 9 18 27 36 45 54 63 72 81
Various Usage of for Loop
Case 1: Add new elements to empty list
A = []
for _ in range(9): # from 0th index to 8th index, total 9
value = int(input("Enter a number: "))
A.append(value)
print(max(A))
Explanation:
_
in for loop is used when we don’t need to care about the iterator of for loop.
Case 2: List Comprehension
General case:
numbers = [1, 2, 3, 4]
result = [] # empty list
for num in numbers:
result.append(num * 3)
print(result) # [3, 6, 9, 12]
Using List Comprehension:
numbers = [1, 2, 3, 4]
result = [num * 3 for num in numbers]
print(result) # [3, 6, 9, 12]
More List Comprehension Examples
# With condition
even_squares = [x**2 for x in range(10) if x % 2 == 0]
# Result: [0, 4, 16, 36, 64]
# With string operations
words = ['hello', 'world', 'python']
uppercase = [word.upper() for word in words]
# Result: ['HELLO', 'WORLD', 'PYTHON']
# Nested list comprehension
matrix = [[i*j for j in range(1, 4)] for i in range(1, 4)]
# Result: [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
enumerate() Function
By using enumerate()
, you can get a counter and the value from the iterable simultaneously. Basic syntax is to put an iterable object inside the parentheses of enumerate().
It returns tuple type (pair of index and element).
Example 1: Basic enumerate
numbers = [1, 2, 3, 4, 5, 6]
for n in enumerate(numbers):
print(n)
# Output:
# (0, 1)
# (1, 2)
# (2, 3)
# (3, 4)
# (4, 5)
# (5, 6)
Example 2: Unpacking enumerate
t = [1, 5, 7, 33, 39, 52]
for i, v in enumerate(t):
print("index: {}, value: {}".format(i, v))
# Output:
# index: 0, value: 1
# index: 1, value: 5
# index: 2, value: 7
# index: 3, value: 33
# index: 4, value: 39
# index: 5, value: 52
Example 3: enumerate with start parameter
fruits = ['apple', 'banana', 'orange']
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. orange
Loop Control Summary
Statement | Purpose | Usage |
---|---|---|
break | Exit loop completely | Use when you want to stop the loop |
continue | Skip current iteration | Use when you want to skip to next iteration |
pass | Do nothing (placeholder) | Use as a placeholder for future code |
Examples
# break example
for i in range(10):
if i == 5:
break
print(i)
# Output: 0, 1, 2, 3, 4
# continue example
for i in range(5):
if i == 2:
continue
print(i)
# Output: 0, 1, 3, 4
# pass example
for i in range(5):
if i == 2:
pass # TODO: implement special logic later
print(i)
# Output: 0, 1, 2, 3, 4
Nested Loops
# Pattern printing
for i in range(5):
for j in range(i + 1):
print("*", end="")
print()
# Output:
# *
# **
# ***
# ****
# *****
Loop with else
Python loops can have an else
clause that executes when the loop completes normally (not via break
).
# for-else
for i in range(5):
print(i)
else:
print("Loop completed normally")
# while-else with break
numbers = [1, 2, 3, 4, 5]
target = 7
for num in numbers:
if num == target:
print(f"Found {target}")
break
else:
print(f"{target} not found in the list")
---
## **Technical Interview Essentials**
### Time Complexity of Loops
**Critical Knowledge for Interviews:**
```python
# Single loop: O(n)
for i in range(n):
print(i) # O(n) total
# Nested loops: O(n²)
for i in range(n):
for j in range(n):
print(i, j) # O(n²) total
# Asymmetric nested loops: O(n²) but fewer operations
for i in range(n):
for j in range(i): # Inner loop runs 0, 1, 2, ..., n-1 times
print(i, j) # Total: 0+1+2+...+(n-1) = n(n-1)/2 = O(n²)
# Loop with early termination: O(n) worst case, could be better
for i in range(n):
if condition_met:
break # Best case: O(1), Worst case: O(n)
process(i)
Common Interview Patterns
1. Two Pointers Pattern
def two_sum_sorted(arr, target):
"""
Find two numbers that add up to target in sorted array.
Time: O(n), Space: O(1)
"""
left, right = 0, len(arr) - 1
while left < right:
current_sum = arr[left] + arr[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return []
def reverse_array(arr):
"""Reverse array in-place using two pointers."""
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
return arr
# Test
print(two_sum_sorted([1, 2, 3, 4, 6], 6)) # [1, 3] (2+4=6)
print(reverse_array([1, 2, 3, 4, 5])) # [5, 4, 3, 2, 1]
2. Sliding Window Pattern
def max_subarray_sum(arr, k):
"""
Find maximum sum of k consecutive elements.
Time: O(n), Space: O(1)
"""
if len(arr) < k:
return None
# Calculate sum of first window
window_sum = sum(arr[:k])
max_sum = window_sum
# Slide the window
for i in range(k, len(arr)):
# Remove leftmost element, add rightmost element
window_sum = window_sum - arr[i - k] + arr[i]
max_sum = max(max_sum, window_sum)
return max_sum
def min_window_with_sum(arr, target_sum):
"""
Find minimum window length with sum >= target_sum.
Time: O(n), Space: O(1)
"""
min_length = float('inf')
window_sum = 0
window_start = 0
for window_end in range(len(arr)):
window_sum += arr[window_end]
# Shrink window until sum is less than target
while window_sum >= target_sum:
min_length = min(min_length, window_end - window_start + 1)
window_sum -= arr[window_start]
window_start += 1
return min_length if min_length != float('inf') else 0
# Test
print(max_subarray_sum([1, 2, 3, 4, 5], 3)) # 12 (3+4+5)
print(min_window_with_sum([1, 2, 3, 4], 6)) # 2 (3+4=7 >= 6)
3. Matrix Traversal Patterns
def spiral_matrix(matrix):
"""
Traverse matrix in spiral order - classic interview question.
Time: O(m*n), Space: O(1) excluding output
"""
if not matrix or not matrix[0]:
return []
result = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
# Traverse right
for col in range(left, right + 1):
result.append(matrix[top][col])
top += 1
# Traverse down
for row in range(top, bottom + 1):
result.append(matrix[row][right])
right -= 1
if top <= bottom:
# Traverse left
for col in range(right, left - 1, -1):
result.append(matrix[bottom][col])
bottom -= 1
if left <= right:
# Traverse up
for row in range(bottom, top - 1, -1):
result.append(matrix[row][left])
left += 1
return result
def diagonal_traverse(matrix):
"""Traverse matrix diagonally."""
if not matrix or not matrix[0]:
return []
rows, cols = len(matrix), len(matrix[0])
result = []
# Upper diagonals (including main diagonal)
for d in range(rows + cols - 1):
diagonal = []
# Determine start position for this diagonal
if d < rows:
start_row, start_col = d, 0
else:
start_row, start_col = rows - 1, d - rows + 1
# Collect diagonal elements
while start_row >= 0 and start_col < cols:
diagonal.append(matrix[start_row][start_col])
start_row -= 1
start_col += 1
# Reverse every other diagonal for zigzag pattern
if d % 2 == 1:
diagonal.reverse()
result.extend(diagonal)
return result
# Test
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(spiral_matrix(matrix)) # [1, 2, 3, 6, 9, 8, 7, 4, 5]
print(diagonal_traverse(matrix)) # [1, 2, 4, 7, 5, 3, 6, 8, 9]
Advanced Loop Techniques
1. Generator Expressions vs List Comprehensions
# Memory comparison for large datasets
n = 1000000
# List comprehension - creates entire list in memory
squares_list = [x**2 for x in range(n)] # ~8MB memory
# Generator expression - lazy evaluation
squares_gen = (x**2 for x in range(n)) # ~200 bytes memory
# Usage patterns
def sum_of_squares_memory_efficient(n):
"""Memory-efficient calculation using generator"""
return sum(x**2 for x in range(n))
def sum_of_squares_memory_intensive(n):
"""Memory-intensive approach using list"""
return sum([x**2 for x in range(n)])
# For interview: generators are better for large datasets
2. Iterator Protocol Implementation
class Fibonacci:
"""Custom iterator for Fibonacci sequence - interview question"""
def __init__(self, max_count):
self.max_count = max_count
self.count = 0
self.current, self.next_val = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.count >= self.max_count:
raise StopIteration
if self.count == 0:
self.count += 1
return self.current
result = self.next_val
self.current, self.next_val = self.next_val, self.current + self.next_val
self.count += 1
return result
# Usage
fib = Fibonacci(10)
print(list(fib)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# Can be used in loops
for num in Fibonacci(5):
print(num, end=" ") # 0 1 1 2 3
3. Loop Optimization Patterns
# SLOW - function call in every iteration
def slow_loop(items):
for i in range(len(items)): # len() called every iteration
process(items[i])
# FAST - cache function result
def fast_loop(items):
length = len(items) # len() called once
for i in range(length):
process(items[i])
# FASTEST - direct iteration
def fastest_loop(items):
for item in items: # No indexing needed
process(item)
# Loop with expensive condition check
def optimized_search(items, condition_func):
"""Move expensive checks outside loop when possible"""
# Pre-filter if possible
candidates = [item for item in items if cheap_check(item)]
# Then apply expensive check
for item in candidates:
if condition_func(item):
return item
return None
Common Interview Gotchas
1. Modifying List While Iterating
# WRONG - modifying list while iterating
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num % 2 == 0:
numbers.remove(num) # BUG: skips elements!
# CORRECT - iterate over copy
numbers = [1, 2, 3, 4, 5]
for num in numbers[:]: # numbers[:] creates a copy
if num % 2 == 0:
numbers.remove(num)
# BETTER - use list comprehension
numbers = [1, 2, 3, 4, 5]
numbers = [num for num in numbers if num % 2 != 0]
# BEST - iterate backwards when removing by index
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
del numbers[i]
2. Variable Scope in Loops
# Common interview trap - variable scope
functions = []
# WRONG - all functions reference the same variable
for i in range(3):
functions.append(lambda: i) # All will return 2!
print([f() for f in functions]) # [2, 2, 2]
# CORRECT - capture variable value
functions = []
for i in range(3):
functions.append(lambda x=i: x) # Capture current value
print([f() for f in functions]) # [0, 1, 2]
# OR use closure properly
functions = []
for i in range(3):
def make_func(value):
return lambda: value
functions.append(make_func(i))
print([f() for f in functions]) # [0, 1, 2]
3. Infinite Loop Detection
def safe_while_loop(start, condition_func, update_func, max_iterations=1000):
"""Safe while loop with iteration limit to prevent infinite loops"""
current = start
iterations = 0
while condition_func(current) and iterations < max_iterations:
current = update_func(current)
iterations += 1
# Safety check for interview scenarios
if iterations >= max_iterations:
raise RuntimeError(f"Loop exceeded {max_iterations} iterations")
return current, iterations
# Example usage
def find_fixed_point(start_value):
"""Find fixed point where f(x) = x"""
def condition(x):
return abs(x - (x**2 + 1) / (2*x)) > 0.0001 # Not converged yet
def update(x):
return (x**2 + 1) / (2*x) # Newton's method iteration
try:
result, iters = safe_while_loop(start_value, condition, update)
return f"Converged to {result:.6f} in {iters} iterations"
except RuntimeError as e:
return f"Failed to converge: {e}"
print(find_fixed_point(2.0))
Performance Tips for Interviews
# 1. Use enumerate instead of range(len())
# SLOW
items = ['a', 'b', 'c', 'd']
for i in range(len(items)):
print(f"{i}: {items[i]}")
# FAST
for i, item in enumerate(items):
print(f"{i}: {item}")
# 2. Use zip for parallel iteration
# SLOW
names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
for i in range(len(names)):
print(f"{names[i]} is {ages[i]} years old")
# FAST
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# 3. Use itertools for efficient combinations
import itertools
# Generate all pairs efficiently
items = [1, 2, 3, 4]
for pair in itertools.combinations(items, 2):
print(pair) # (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)
# Cartesian product
for combo in itertools.product([1, 2], ['a', 'b']):
print(combo) # (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')