For JavaScript Developers: Differences in Loop Constructs

Week 2 Day 2: Control Flow Fundamentals

Introduction

As a JavaScript developer transitioning to Python, you'll find that while the core concepts of loops remain similar, there are significant differences in syntax, behavior, and available loop constructs. This guide will help you understand how loops in Python differ from those in JavaScript, highlighting key similarities and differences to help you write idiomatic Python code.

The code for this lesson can be found in the /week2/day2/js_vs_python_loops.py file in your course repository.

For Loops: Fundamental Differences

The most significant difference you'll encounter is that Python's for loop is fundamentally different from JavaScript's. In Python, for loops are designed to iterate directly over sequences (like lists, strings, tuples) or other iterables, rather than incrementing a counter as in JavaScript.

JavaScript For Loop

In JavaScript, the traditional for loop has three parts: initialization, condition, and increment:

// JavaScript for loop
for (let i = 0; i < 5; i++) {
    console.log(i);  // Outputs 0, 1, 2, 3, 4
}

Python For Loop

In Python, the for loop directly iterates over a sequence:

# Python for loop
for i in range(5):
    print(i)  # Outputs 0, 1, 2, 3, 4

Notice that Python uses range(5) to create a sequence of numbers from 0 to 4. The Python for loop is actually more similar to JavaScript's for...of loop, which was introduced in ES6.

JavaScript for...of Loop

// JavaScript for...of loop (similar to Python's for loop)
const items = ["apple", "banana", "cherry"];
for (const item of items) {
    console.log(item);  // Outputs each item in the array
}

Python For Loop with List

# Python for loop with a list
items = ["apple", "banana", "cherry"]
for item in items:
    print(item)  # Outputs each item in the list

Key Differences

The range() Function vs JavaScript Counter Loops

In JavaScript, you often use counter-based loops for numeric iterations. In Python, the range() function serves this purpose by generating a sequence of numbers to iterate over.

JavaScript Counter Loop

// JavaScript counter loop
for (let i = 0; i < 10; i++) {
    console.log(i);
}

// Starting from a different value
for (let i = 5; i < 15; i++) {
    console.log(i);
}

// Using a different step size
for (let i = 0; i < 20; i += 2) {
    console.log(i);  // Even numbers 0 to 18
}

Python range() Function

# Python range() with one argument (stop value)
for i in range(10):  # 0 to 9
    print(i)

# Python range() with two arguments (start, stop)
for i in range(5, 15):  # 5 to 14
    print(i)

# Python range() with three arguments (start, stop, step)
for i in range(0, 20, 2):  # Even numbers 0 to 18
    print(i)

Important Differences

Creating a List with range()

# Convert range to a list in Python
numbers = list(range(5))
print(numbers)  # [0, 1, 2, 3, 4]

# JavaScript equivalent using Array.from()
// const numbers = Array.from({ length: 5 }, (_, i) => i);
// console.log(numbers);  // [0, 1, 2, 3, 4]

For...in Loops: A Common Confusion

One of the most common confusions for JavaScript developers is the difference between JavaScript's for...in loop and Python's for...in loop. Despite the identical syntax, they behave quite differently.

JavaScript for...in Loop

In JavaScript, for...in loops iterate over the enumerable properties (keys) of an object:

// JavaScript for...in loop iterates over object properties (keys)
const person = { name: "Alice", age: 30, job: "Developer" };
for (const key in person) {
    console.log(key + ": " + person[key]);
}
// Outputs:
// name: Alice
// age: 30
// job: Developer

// CAUTION: Using for...in with arrays in JavaScript is discouraged
const arr = ["apple", "banana", "cherry"];
for (const index in arr) {
    console.log(index + ": " + arr[index]);
}
// Outputs:
// 0: apple
// 1: banana
// 2: cherry

Python for...in Loop

In Python, for...in loops iterate over the elements (values) of a sequence:

# Python for loop with a list (iterates over values)
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
# Outputs:
# apple
# banana
# cherry

# Python for loop with a dictionary (iterates over keys by default)
person = {"name": "Alice", "age": 30, "job": "Developer"}
for key in person:
    print(key + ": " + str(person[key]))
# Outputs:
# name: Alice
# age: 30
# job: Developer

Critical Difference

The key difference to remember is:

In Python, when iterating over a dictionary with a simple for...in loop, you iterate over the keys, not the values. This behavior is similar to JavaScript's for...in, but for completely different reasons.

Dictionary Iteration: More Options in Python

Python provides more flexible ways to iterate over dictionaries than JavaScript does for objects.

JavaScript Object Iteration

// JavaScript object iteration
const person = { name: "Alice", age: 30, job: "Developer" };

// Iterating over keys (old way)
for (const key in person) {
    console.log(key + ": " + person[key]);
}

// Modern alternatives for object iteration:

// Get keys as an array
console.log(Object.keys(person));  // ["name", "age", "job"]

// Get values as an array
console.log(Object.values(person));  // ["Alice", 30, "Developer"]

// Get key-value pairs as arrays
console.log(Object.entries(person));  // [["name", "Alice"], ["age", 30], ["job", "Developer"]]

// Using Object.entries() with for...of
for (const [key, value] of Object.entries(person)) {
    console.log(key + ": " + value);
}

Python Dictionary Iteration

# Python dictionary iteration
person = {"name": "Alice", "age": 30, "job": "Developer"}

# Iterate over keys (default behavior)
for key in person:
    print(key + ": " + str(person[key]))

# Explicit methods for dictionary iteration:

# Get all keys
print(person.keys())  # dict_keys(['name', 'age', 'job'])

# Get all values
print(person.values())  # dict_values(['Alice', 30, 'Developer'])

# Get key-value pairs as tuples
print(person.items())  # dict_items([('name', 'Alice'), ('age', 30), ('job', 'Developer')])

# Using .items() to get both key and value directly
for key, value in person.items():
    print(key + ": " + str(value))

Key Differences

While Loops: Mostly Similar

While loops are the construct with the most similarity between JavaScript and Python.

JavaScript While Loop

// JavaScript while loop
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// JavaScript do...while loop
let i = 0;
do {
    console.log("This will run at least once: " + i);
    i++;
} while (i < 3);

Python While Loop

# Python while loop
count = 0
while count < 5:
    print(count)
    count += 1

# Python doesn't have a do...while loop
# To simulate it, you need to structure the code differently:
i = 0
while True:
    print("This will run at least once: " + str(i))
    i += 1
    if i >= 3:
        break

Key Differences

Loop Control: break, continue, else

Both JavaScript and Python provide break and continue for loop control, but Python adds an unusual else clause for loops.

JavaScript Loop Control

// JavaScript break
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break;  // Exit the loop
    }
    console.log(i);
}

// JavaScript continue
for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) {
        continue;  // Skip to the next iteration
    }
    console.log(i);  // Only odd numbers
}

Python Loop Control

# Python break
for i in range(10):
    if i == 5:
        break  # Exit the loop
    print(i)

# Python continue
for i in range(10):
    if i % 2 == 0:
        continue  # Skip to the next iteration
    print(i)  # Only odd numbers

Python's Unique "else" Clause for Loops

Python has a unique feature: you can add an else clause to loops. The else block executes after the loop completes normally (without a break).

# Python for loop with else
for i in range(5):
    print(i)
else:
    print("Loop completed successfully")

# The else clause won't execute if the loop is exited with a break
for i in range(5):
    print(i)
    if i == 2:
        break
else:
    print("This won't execute because the loop was broken")

This construct is especially useful for search loops:

# Finding an item in a list
def find_item(items, target):
    for item in items:
        if item == target:
            print(f"Found {target}!")
            break
    else:
        print(f"{target} not found in the list")

find_item([1, 2, 3, 4, 5], 3)  # Found 3!
find_item([1, 2, 3, 4, 5], 6)  # 6 not found in the list

There's no direct equivalent in JavaScript. You would typically use a flag variable or a conditional after the loop:

// JavaScript equivalent using a flag
function findItem(items, target) {
    let found = false;
    for (const item of items) {
        if (item === target) {
            console.log(`Found ${target}!`);
            found = true;
            break;
        }
    }
    if (!found) {
        console.log(`${target} not found in the list`);
    }
}

List Comprehensions: Python's Powerful Alternative to map() and filter()

Python provides a powerful feature called "list comprehensions" that can often replace loops for creating lists. This is somewhat similar to JavaScript's map() and filter() methods, but with a more concise syntax.

JavaScript Array Transformations

// JavaScript map()
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(num => num * num);
console.log(squared);  // [1, 4, 9, 16, 25]

// JavaScript filter()
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens);  // [2, 4]

// Combining map() and filter()
const squaredEvens = numbers
    .filter(num => num % 2 === 0)
    .map(num => num * num);
console.log(squaredEvens);  // [4, 16]

Python List Comprehensions

# Python list comprehension (like JavaScript's map())
numbers = [1, 2, 3, 4, 5]
squared = [num * num for num in numbers]
print(squared)  # [1, 4, 9, 16, 25]

# Python list comprehension with condition (like JavaScript's filter())
evens = [num for num in numbers if num % 2 == 0]
print(evens)  # [2, 4]

# Combining mapping and filtering in a single comprehension
squared_evens = [num * num for num in numbers if num % 2 == 0]
print(squared_evens)  # [4, 16]

More Complex Comprehensions

Python comprehensions can be more powerful than they might initially seem:

# Nested loops in a comprehension
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Comprehensions for dictionaries (dict comprehensions)
squares_dict = {num: num * num for num in range(1, 6)}
print(squares_dict)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Comprehensions for sets (set comprehensions)
letters = {char for char in "mississippi"}
print(letters)  # {'m', 'i', 's', 'p'}

The JavaScript equivalent would be much more verbose:

// JavaScript equivalent for nested loops
const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const flattened = matrix.reduce((acc, row) => acc.concat(row), []);
console.log(flattened);

// JavaScript equivalent for dictionary comprehension
const squaresDict = Object.fromEntries(
    Array.from({ length: 5 }, (_, i) => i + 1).map(num => [num, num * num])
);
console.log(squaresDict);

Iteration Protocols: Under the Hood

Both JavaScript and Python have underlying protocols for iteration, but they work differently.

JavaScript Iterator Protocol

// JavaScript custom iterable
const customIterable = {
    data: [1, 2, 3, 4, 5],
    
    // Define the iterator method
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                if (index < this.data.length) {
                    return { value: this.data[index++], done: false };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

// Now we can use our custom iterable in a for...of loop
for (const item of customIterable) {
    console.log(item);
}

Python Iterator Protocol

# Python custom iterable
class CustomIterable:
    def __init__(self):
        self.data = [1, 2, 3, 4, 5]
    
    # Define the __iter__ method
    def __iter__(self):
        self.index = 0
        return self
    
    # Define the __next__ method
    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration  # Signal end of iteration

# Now we can use our custom iterable in a for loop
custom_iterable = CustomIterable()
for item in custom_iterable:
    print(item)

Key Differences

Performance Considerations and Best Practices

As you transition from JavaScript to Python, consider these performance tips and best practices for loops.

Common Performance Patterns

JavaScript
// Avoid array modification during iteration
const items = [1, 2, 3, 4, 5];
// Bad practice:
for (let i = 0; i < items.length; i++) {
    if (items[i] % 2 === 0) {
        items.splice(i, 1);  // Modifies array during iteration
        i--;  // Adjust index
    }
}

// Better approach:
const filtered = items.filter(item => item % 2 !== 0);

// Avoid expensive operations in loop conditions
// Bad practice:
for (let i = 0; i < someArray.length; i++) {
    // length is accessed on each iteration
}

// Better approach:
const length = someArray.length;
for (let i = 0; i < length; i++) {
    // length is accessed once
}
Python
# Avoid list modification during iteration
items = [1, 2, 3, 4, 5]
# Bad practice:
i = 0
while i < len(items):
    if items[i] % 2 == 0:
        items.pop(i)  # Modifies list during iteration
    else:
        i += 1  # Only increment if we didn't remove

# Better approach:
items = [item for item in items if item % 2 != 0]
# or
filtered = [item for item in items if item % 2 != 0]

# Using built-in functions instead of loops
numbers = [1, 2, 3, 4, 5]
# Instead of manually summing:
total = 0
for num in numbers:
    total += num

# Use the built-in sum() function:
total = sum(numbers)

Pythonic Loop Practices

Here are some idiomatic Python loop patterns that might be new to JavaScript developers:

# Enumerate for getting index and value together
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

# Zip for parallel iteration
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

# Unpacking in loops
points = [(1, 2), (3, 4), (5, 6)]
for x, y in points:
    print(f"Point: ({x}, {y})")

# Looping with default dictionary
from collections import defaultdict
word_counts = defaultdict(int)
for word in ["apple", "banana", "apple", "cherry"]:
    word_counts[word] += 1
print(dict(word_counts))  # {'apple': 2, 'banana': 1, 'cherry': 1}

Common Pitfalls for JavaScript Developers

# Pitfall 1: Creating a list of functions that capture the loop variable
# In JavaScript, each closure captures its own copy of i due to let's block scope
functions = []
for i in range(5):
    functions.append(lambda: i)  # All functions capture the same i

for f in functions:
    print(f())  # Prints 4, 4, 4, 4, 4

# Fix: Pass the value as a default argument
functions = []
for i in range(5):
    functions.append(lambda i=i: i)  # Each i is bound separately

for f in functions:
    print(f())  # Prints 0, 1, 2, 3, 4

# Pitfall 2: Creating nested lists incorrectly
# This doesn't create independent inner lists
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] - all rows changed!

# Fix: Use a list comprehension
grid = [[0 for _ in range(3)] for _ in range(3)]
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]] - only first row changed

Real-World Comparison Example

Let's look at a complete example implemented in both JavaScript and Python to see the differences in loop usage in a real-world scenario.

Task: Process Sales Data

We'll implement a function to process sales data, calculate statistics, and find top products.

JavaScript Implementation
// JavaScript version
function processSalesData(salesData) {
    // Calculate total revenue
    let totalRevenue = 0;
    for (const sale of salesData) {
        totalRevenue += sale.price * sale.quantity;
    }
    
    // Group sales by category
    const categorySales = {};
    for (const sale of salesData) {
        const category = sale.category;
        if (!categorySales[category]) {
            categorySales[category] = 0;
        }
        categorySales[category] += sale.price * sale.quantity;
    }
    
    // Find top selling products
    const productSales = {};
    for (const sale of salesData) {
        const product = sale.product;
        if (!productSales[product]) {
            productSales[product] = 0;
        }
        productSales[product] += sale.quantity;
    }
    
    // Convert to array and sort by quantity
    const sortedProducts = Object.entries(productSales)
        .map(([product, quantity]) => ({ product, quantity }))
        .sort((a, b) => b.quantity - a.quantity);
    
    const topProducts = sortedProducts.slice(0, 3);
    
    // Calculate monthly sales
    const monthlySales = {};
    for (const sale of salesData) {
        const month = new Date(sale.date).getMonth() + 1;
        if (!monthlySales[month]) {
            monthlySales[month] = 0;
        }
        monthlySales[month] += sale.price * sale.quantity;
    }
    
    return {
        totalRevenue,
        categorySales,
        topProducts,
        monthlySales
    };
}

// Example usage
const salesData = [
    { product: "Laptop", category: "Electronics", price: 999.99, quantity: 3, date: "2023-01-15" },
    { product: "Smartphone", category: "Electronics", price: 699.99, quantity: 5, date: "2023-02-20" },
    { product: "Headphones", category: "Accessories", price: 149.99, quantity: 10, date: "2023-01-30" },
    { product: "Monitor", category: "Electronics", price: 299.99, quantity: 2, date: "2023-03-10" },
    { product: "Keyboard", category: "Accessories", price: 79.99, quantity: 7, date: "2023-02-05" }
];

const results = processSalesData(salesData);
console.log(results);
Python Implementation
# Python version
from datetime import datetime
from collections import defaultdict

def process_sales_data(sales_data):
    # Calculate total revenue
    total_revenue = sum(sale["price"] * sale["quantity"] for sale in sales_data)
    
    # Group sales by category
    category_sales = defaultdict(float)
    for sale in sales_data:
        category_sales[sale["category"]] += sale["price"] * sale["quantity"]
    
    # Find top selling products
    product_sales = defaultdict(int)
    for sale in sales_data:
        product_sales[sale["product"]] += sale["quantity"]
    
    # Convert to list and sort by quantity
    sorted_products = sorted(
        [{"product": product, "quantity": quantity} 
         for product, quantity in product_sales.items()],
        key=lambda x: x["quantity"],
        reverse=True
    )
    
    top_products = sorted_products[:3]
    
    # Calculate monthly sales
    monthly_sales = defaultdict(float)
    for sale in sales_data:
        month = datetime.strptime(sale["date"], "%Y-%m-%d").month
        monthly_sales[month] += sale["price"] * sale["quantity"]
    
    return {
        "total_revenue": total_revenue,
        "category_sales": dict(category_sales),
        "top_products": top_products,
        "monthly_sales": dict(monthly_sales)
    }

# Example usage
sales_data = [
    {"product": "Laptop", "category": "Electronics", "price": 999.99, "quantity": 3, "date": "2023-01-15"},
    {"product": "Smartphone", "category": "Electronics", "price": 699.99, "quantity": 5, "date": "2023-02-20"},
    {"product": "Headphones", "category": "Accessories", "price": 149.99, "quantity": 10, "date": "2023-01-30"},
    {"product": "Monitor", "category": "Electronics", "price": 299.99, "quantity": 2, "date": "2023-03-10"},
    {"product": "Keyboard", "category": "Accessories", "price": 79.99, "quantity": 7, "date": "2023-02-05"}
]

results = process_sales_data(sales_data)
print(results)

Key Differences in This Example

Conclusion

As a JavaScript developer learning Python, understanding the differences in loop constructs will help you write more idiomatic and efficient Python code. Here are the key takeaways:

  1. Python's for loop is most similar to JavaScript's for...of, not the traditional for loop
  2. Use range() in Python to create sequences of numbers for iteration
  3. Python's for...in on a dictionary iterates over keys, but it's more common to use .items() to get both keys and values
  4. Python's list comprehensions can often replace map() and filter() operations with more concise syntax
  5. Python has an unique else clause for loops that executes when the loop completes without a break
  6. Python's enumerate() and zip() functions provide powerful ways to iterate over sequences

By understanding these differences and embracing Python's idiomatic patterns, you'll be able to write more effective and Pythonic code, leveraging your JavaScript knowledge while adopting Python's unique strengths.

Practice Exercises

Try these exercises to solidify your understanding of the differences between JavaScript and Python loops:

  1. Convert JavaScript to Python: Convert this JavaScript for loop to Python:

    // JavaScript
    const numbers = [];
    for (let i = 0; i < 10; i += 2) {
        numbers.push(i * i);
    }
    console.log(numbers);
  2. Convert JavaScript to Pythonic: Convert this JavaScript code to idiomatic Python using list comprehension:

    // JavaScript
    const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    const evenSquares = numbers
        .filter(n => n % 2 === 0)
        .map(n => n * n);
  3. Using Python's Loop Else: Write a Python function that checks if a list contains any prime numbers, using the loop else clause.

  4. Parallel Iteration: Write a function in both JavaScript and Python that takes two arrays/lists of equal length and returns an array/list of pairs.

  5. Dictionary Processing: Write a function in both JavaScript and Python that filters a dictionary/object to include only key-value pairs where the value meets a certain condition.

  6. Advanced Challenge: Implement a function in both languages that finds all possible pairs of numbers from an array/list that sum to a target value. Try to make your solutions as idiomatic as possible in both languages.