Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Python
  3. Python Loops Practice: range(), enumerate(), zip(), break/continue, for-else
Python70 exercises

Python Loops Practice: range(), enumerate(), zip(), break/continue, for-else

Practice Python loop patterns: why range() excludes stop, enumerate with start=1, zip(strict=True) for length checking, for-else for "no break" detection, and safe iteration over dicts.

Cheat SheetCommon ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Iterate over items and print each item

On this page
  1. 1range() cheat sheet
  2. Making range inclusive
  3. The _ convention: ignoring the loop variable
  4. 2enumerate(): index + value together
  5. The start parameter
  6. 3zip(): parallel iteration
  7. The silent truncation problem
  8. zip(strict=True): catch length mismatches
  9. zip with more than two iterables
  10. 4for-else: the "no break" pattern
  11. When for-else is useful
  12. What skips the else block?
  13. while-else works the same way
  14. 5Dict iteration patterns
  15. Keys and values together
  16. Just keys (the default)
  17. Just values
  18. With enumerate
  19. 6break and continue
  20. break: exit the loop entirely
  21. continue: skip to next iteration
  22. pass: placeholder for empty loops
  23. In nested loops
  24. 7while loops
  25. Common while patterns
  26. Infinite loop trap
  27. 8Common patterns
  28. Loop with index (prefer enumerate)
  29. Iterate in sorted order
  30. Iterate in reverse
  31. Iterate with lookahead (pairwise)
  32. 9References
range() cheat sheetenumerate(): index + value togetherzip(): parallel iterationfor-else: the "no break" patternDict iteration patternsbreak and continuewhile loopsCommon patternsReferences

Python loops are iterator-based, not counter-based. That's the theory. In practice, you blank on enumerate's syntax, forget zip stops at the shortest iterable, and mix up range's arguments.

These exercises cover the patterns that trip people up: why range(5) gives you 0-4 (not 0-5), how enumerate(start=1) works, what zip(strict=True) does, and when for-else actually runs.

Quick reference:

  • range(stop) → 0 to stop-1 (stop is excluded)
  • enumerate(items, start=1) → index starts at 1, not 0
  • zip(a, b, strict=True) → raises ValueError if lengths differ
  • for...else → else runs only if no break occurred
Related Python Topics
Python Comprehensions: List, Dict, Set & Generator ExpressionsPython Function Arguments: defaults, *args, keyword-only, **kwargsPython Collections: Lists, Dicts, Sets & Tuples

range(stop) excludes stop—range(5) gives 0-4, not 0-5. Add 1 for inclusive: range(1, 10+1). Python 3's range is lazy and uses nearly zero memory.

range(stop)             # 0, 1, 2, ..., stop-1
range(start, stop)      # start, start+1, ..., stop-1
range(start, stop, step) # start, start+step, ..., < stop

The key insight: stop is excluded. range(5) gives you 0, 1, 2, 3, 4—not 0 through 5.

CallProducesCount
range(5)0, 1, 2, 3, 45 elements
range(1, 6)1, 2, 3, 4, 55 elements
range(0, 10, 2)0, 2, 4, 6, 85 elements
range(5, 0, -1)5, 4, 3, 2, 15 elements

Why exclusive? So that range(n) produces exactly n values, and range(len(items)) gives valid indices for 0-based indexing.

Memory efficient: Unlike Python 2's range() which built a full list, Python 3's range() is a lazy sequence object that stores only start, stop, and step—a range(1_000_000) uses just 48 bytes regardless of size. It also supports O(1) membership testing: 999_999 in range(1_000_000) is instant because range checks mathematically rather than iterating. For a printable reference of loop patterns, see the Python loops cheat sheet.

Making range inclusive

If you need the stop value included, add 1:

# Include 10 in the range
for i in range(1, 10 + 1):  # 1 through 10
    print(i)

The _ convention: ignoring the loop variable

When you don't need the loop variable, use _ by convention:

# Run something 5 times, don't care about the index
for _ in range(5):
    do_something()

This signals to readers (and linters) that you're intentionally ignoring the value.


Ready to practice?

Start practicing Python Loops: range(), enumerate(), zip(), break/continue, for-else with spaced repetition

Use enumerate(items) instead of manual index tracking. For 1-based numbering, pass start=1. It yields (index, value) tuples that unpack in the for loop.

Instead of this anti-pattern:

# BAD: manual index tracking
i = 0
for item in items:
    print(f"{i}: {item}")
    i += 1

Use enumerate:

# GOOD: enumerate handles the index
for i, item in enumerate(items):
    print(f"{i}: {item}")

How it works: enumerate() yields tuples like (0, 'apple'), (1, 'banana'). Python's tuple unpacking splits each tuple into separate variables i and item.

The start parameter

By default, enumerate starts at 0. For 1-based numbering (human-readable lists, line numbers), use start=:

names = ["Alice", "Bob", "Charlie"]

for num, name in enumerate(names, start=1):
    print(f"{num}. {name}")
# Output:
# 1. Alice
# 2. Bob
# 3. Charlie

You can start at any integer:

# Continue numbering from where you left off
for i, item in enumerate(batch2, start=len(batch1)):
    ...

zip stops at the shortest iterable by default—silently losing data. Use zip(strict=True) (Python 3.10+) when mismatched lengths indicate a bug.

zip() pairs up elements from multiple iterables:

names = ["Alice", "Bob"]
scores = [85, 92]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Alice: 85
# Bob: 92

Like enumerate(), this uses tuple unpacking: zip() yields tuples like ('Alice', 85), and Python unpacks them into name and score.

The silent truncation problem

By default, zip stops at the shortest iterable—a design PEP 618 called out as a common source of hard-to-find bugs, which is why strict=True was added in Python 3.10:

names = ["Alice", "Bob", "Charlie"]
scores = [85, 92]  # Missing Charlie's score!

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Only prints Alice and Bob—Charlie is silently lost!

This is a common source of data-loss bugs after refactoring.

zip(strict=True): catch length mismatches

Python 3.10+ added strict=True to raise an error if lengths differ:

for name, score in zip(names, scores, strict=True):
    print(f"{name}: {score}")
# ValueError: zip() argument 2 is shorter than argument 1

When to use strict=True:

  • When mismatched lengths indicate a bug (parallel data that should align)
  • Data pipelines where silent truncation would corrupt results
  • Any time you're zipping data that must be the same length

Note: The error is raised when iteration reaches the mismatch, not at the start. This is because iterables might be lazy.

zip with more than two iterables

for a, b, c in zip(list1, list2, list3):
    process(a, b, c)

Python's for-else is unusual and has no equivalent in most other languages. If you work across languages, Python vs JavaScript loops compared covers this and other key differences. The else block runs only if the loop completed without breaking.

for item in items:
    if matches(item):
        print("Found it!")
        break
else:
    print("Not found")  # Only runs if no break occurred

Think of it as "no-break else"—if break never executed, else runs.

for-else control flow: if break occurs the else block is skipped, if the loop completes normally the else block runs

When for-else is useful

The classic use case is searching:

# Find first prime factor
for i in range(2, n):
    if n % i == 0:
        print(f"Smallest factor: {i}")
        break
else:
    print(f"{n} is prime")  # No factors found

Without for-else, you'd need a flag variable:

# Without for-else (more verbose)
found = False
for i in range(2, n):
    if n % i == 0:
        print(f"Smallest factor: {i}")
        found = True
        break
if not found:
    print(f"{n} is prime")

What skips the else block?

  • break → else is skipped
  • return → function exits, else is skipped
  • Raised exception → else is skipped
  • Normal completion → else runs

while-else works the same way

while condition:
    if found:
        break
else:
    print("Loop completed without break")

Keys and values together

config = {"host": "localhost", "port": 8080}

for key, value in config.items():
    print(f"{key} = {value}")

Just keys (the default)

for key in config:  # Same as config.keys()
    print(key)

Just values

for value in config.values():
    print(value)

With enumerate

for i, (key, value) in enumerate(config.items()):
    print(f"{i}: {key} = {value}")

break: exit the loop entirely

for item in items:
    if done_processing(item):
        break  # Exit loop immediately
    process(item)
# Execution continues here after break

continue: skip to next iteration

for item in items:
    if should_skip(item):
        continue  # Skip rest of this iteration
    process(item)  # This line is skipped when continue runs

pass: placeholder for empty loops

pass does nothing—use it when you need a syntactically valid but empty loop body:

# Placeholder while developing
for item in items:
    pass  # TODO: implement later

# Intentionally empty (rare but valid)
while not ready():
    pass  # Busy-wait

break exits the loop entirely, continue skips to the next iteration

In nested loops

break and continue only affect the innermost loop:

for row in matrix:
    for cell in row:
        if cell == target:
            break  # Only breaks inner loop!
    # Outer loop continues

Rust's ownership model handles loop iteration very differently from Python's reference-based approach — see Python vs Rust loops for a side-by-side comparison. To break out of nested loops, use a flag or extract to a function:

def find_in_matrix(matrix, target):
    for row in matrix:
        for cell in row:
            if cell == target:
                return cell  # Exits entire function
    return None

Use while when you don't know how many iterations you need:

# Retry with backoff
attempts = 0
while attempts < max_retries:
    result = try_operation()
    if result:
        break
    attempts += 1
    time.sleep(2 ** attempts)

Common while patterns

Sentinel value:

while True:
    line = input("> ")
    if line == "quit":
        break
    process(line)

Condition-based:

while queue:
    item = queue.pop()
    process(item)

Infinite loop trap

The classic while mistake is forgetting to update the condition:

# BUG: infinite loop!
i = 0
while i < 10:
    print(i)
    # Forgot i += 1!

# FIX: always update the condition variable
i = 0
while i < 10:
    print(i)
    i += 1

Loop with index (prefer enumerate)

# BAD: manual indexing
for i in range(len(items)):
    print(i, items[i])

# GOOD: enumerate
for i, item in enumerate(items):
    print(i, item)

Iterate in sorted order

for item in sorted(items):
    print(item)  # Ascending order

for item in sorted(items, reverse=True):
    print(item)  # Descending order

for item in sorted(items, key=len):
    print(item)  # By length

Iterate in reverse

for item in reversed(items):
    print(item)

# With index (counting down)
for i in range(len(items) - 1, -1, -1):
    print(i, items[i])

Iterate with lookahead (pairwise)

# Compare adjacent items
for i in range(len(items) - 1):
    current, next_item = items[i], items[i + 1]
    if current > next_item:
        print("Decrease at", i)

# Python 3.10+: itertools.pairwise
from itertools import pairwise
for current, next_item in pairwise(items):
    ...

  • Python Tutorial: for Statements
  • Python Tutorial: range()
  • Python Tutorial: break and continue, and else on Loops
  • Python Reference: The for statement
  • Built-in Functions: enumerate()
  • Built-in Functions: zip()
  • PEP 618: Add Optional Length-Checking To zip

When to Use Python Loops: range(), enumerate(), zip(), break/continue, for-else

  • You need to process every item in a list, dict, or string.
  • You need to iterate with an index via enumerate().
  • You want to iterate multiple sequences in parallel with zip().
  • You need to detect "not found" after a search loop (for-else).
  • You're looping a specific number of times with range().

Check Your Understanding: Python Loops: range(), enumerate(), zip(), break/continue, for-else

Prompt

Print each item with its 1-based index from a list of names.

What a strong answer looks like

Use enumerate(names, start=1) to get (i, name) pairs starting at 1. It avoids manual index tracking and is idiomatic Python.

What You'll Practice: Python Loops: range(), enumerate(), zip(), break/continue, for-else

for loop with range(start, stop, step)range() semantics: stop is exclusive, lazy evaluationenumerate() for index + value (tuple unpacking)enumerate(start=1) for 1-based indexingzip() for parallel iterationzip(strict=True) for length checkingDict iteration: .items(), .keys(), .values()while loops and break/continue/passfor-else for "no break" detectionUsing _ for ignored loop variablessorted() for ordered iteration

Common Python Loops: range(), enumerate(), zip(), break/continue, for-else Pitfalls

  • Off-by-one errors with range(): stop is excluded, not included
  • Forgetting enumerate(start=1) when you need 1-based indexing
  • zip() silently truncating when iterables have different lengths—use strict=True
  • Misunderstanding for-else: else runs on normal completion, not when condition is false
  • Infinite while loops from forgetting to increment/update the condition
  • Modifying a list while iterating (see collections page for safe patterns)

Python Loops: range(), enumerate(), zip(), break/continue, for-else FAQ

Why does range() not include the stop value?

range(n) produces exactly n values (0 through n-1), which matches 0-based indexing and len(). This makes range(len(items)) iterate over valid indices. To include stop, add 1: range(start, stop+1).

How do I start enumerate at 1 instead of 0?

Pass the start parameter: enumerate(items, start=1). The default is 0. You can use any integer, including negative numbers.

What does zip(strict=True) do?

Added in Python 3.10, strict=True raises a ValueError if the iterables have different lengths. Without it, zip silently truncates to the shortest—a common source of data-loss bugs.

What does for-else mean in Python?

The else block runs only if the loop completes normally—i.e., no break occurred. Think of it as "no-break else". It's useful for search patterns where you need to handle "not found".

When should I use a while loop instead of for?

Use while when the stopping condition is not tied to a collection—such as polling, retries, or reading until EOF. Use for when iterating over a known sequence.

Does return or raise skip the for-else block?

Yes. The else clause only runs if the loop finishes all iterations naturally. break, return, and raised exceptions all skip it.

How do I iterate over a dict with keys and values?

Use for k, v in d.items(): to get key-value pairs. For just keys: for k in d: (or d.keys()). For just values: for v in d.values().

How do I iterate with index AND value?

Use enumerate(): for i, item in enumerate(items). Don't use range(len(items)) and index manually—enumerate is clearer and less error-prone.

What happens if I modify a list while iterating over it?

You'll get unexpected behavior—items can be skipped or processed twice. Iterate over a copy (items[:]) or build a new list with a comprehension. See the collections page for safe patterns.

How do continue and break differ?

break exits the loop entirely. continue skips the rest of the current iteration and moves to the next. Both work in for and while loops.

What does underscore (_) mean in a for loop?

Using _ as the loop variable signals you don't need the value: for _ in range(5): do_something(). It's a convention that tells readers (and linters) you're intentionally ignoring it.

Is range() memory efficient in Python 3?

Yes. Python 3's range() is lazy—it generates values on demand instead of building a list. range(1_000_000) uses almost no memory. This was called xrange() in Python 2.

Python Loops: range(), enumerate(), zip(), break/continue, for-else Syntax Quick Reference

range() basics
for i in range(5):      # 0, 1, 2, 3, 4
for i in range(1, 6):   # 1, 2, 3, 4, 5
for i in range(0, 10, 2): # 0, 2, 4, 6, 8
enumerate()
for i, item in enumerate(items):
    print(f"{i}: {item}")
enumerate(start=1)
for num, name in enumerate(names, start=1):
    print(f"{num}. {name}")  # 1-based
zip() parallel iteration
for a, b in zip(list1, list2):
    print(a, b)
zip(strict=True)
for a, b in zip(xs, ys, strict=True):
    # Raises ValueError if lengths differ
    process(a, b)
Dict iteration
for key, value in my_dict.items():
    print(f"{key} = {value}")
for-else (search pattern)
for item in items:
    if matches(item):
        result = item
        break
else:
    result = None  # Not found
while with break
while True:
    data = fetch()
    if data is None:
        break
    process(data)
continue to skip
for item in items:
    if should_skip(item):
        continue
    process(item)
Ignoring loop variable (_)
for _ in range(3):
    print("Hello")  # Runs 3 times
Iterate in sorted order
for name in sorted(names):
    print(name)  # Alphabetical

Python Loops: range(), enumerate(), zip(), break/continue, for-else Sample Exercises

Example 1Difficulty: 1/5

Iterate over characters in a string called item_name and print each character

for char in item_name:
    print(char)
Example 2Difficulty: 1/5

Write the while loop header that runs while is_running is True

while is_running:
Example 3Difficulty: 1/5

Write a while loop header that runs while item_count is less than max_items

while item_count < max_items:

+ 67 more exercises

Quick Reference
Python Loops: range(), enumerate(), zip(), break/continue, for-else Cheat Sheet →

Copy-ready syntax examples for quick lookup

Also in Other Languages

JavaScript Loops Practice

Start practicing Python Loops: range(), enumerate(), zip(), break/continue, for-else

Free daily exercises with spaced repetition. No credit card required.

← Back to Python Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.