beginner
Fundamentals

Apex For Loops: A Complete Beginner's Guide to Iteration

Learn Apex for loops from scratch with practical examples. Master traditional for loops, list iteration, debugging techniques, and when to use each loop type in Salesforce development.

15 min read
Warren Walters
apex
for-loops
salesforce
list-iteration
debugging
beginner

TL;DR

For loops in Apex let you repeat code a specific number of times, perfect for processing lists of data. There are two main types: traditional for loops (using index positions like i) and list iteration loops (processing each item directly). Both do the same job - traditional loops give you more control, while list iteration is cleaner and easier to read.

What Are For Loops and Why Do They Matter?

Imagine you have a basket of 50 apples, and you need to check each one for bruises. You could check the first apple, then the second, then the third... but writing out those steps 50 times would be ridiculous! That's exactly what for loops solve in programming.

A for loop is a block of code that repeats itself until a condition is met. In Salesforce development, you'll use for loops constantly - to process Account records, update Contact information, calculate totals, and much more. Learning for loops is like learning to ride a bike: once you get it, you'll wonder how you ever lived without it.

Why For Loops Are Essential in Salesforce

As a Salesforce developer, you'll work with collections of data all the time:

  • Processing multiple Account records from a query
  • Updating dozens of Opportunity records at once
  • Validating a list of Contact emails
  • Calculating totals across Order Items

For loops make all of this possible. They're one of the most fundamental skills you'll use every single day.

Understanding Traditional For Loops

Let's start with the traditional for loop - the type you'll see most often in Apex code.

The Anatomy of a Traditional For Loop

A traditional for loop has three parts packed into one line:

for (Integer i = 0; i < 5; i++) {
    // Code to repeat goes here
}

Let's break this down into digestible pieces:

  1. Initial Statement (Integer i = 0): Creates a counter variable and sets its starting value
  2. Condition (i < 5): The loop continues while this is true
  3. Increment (i++): What happens after each loop iteration

Think of it like climbing stairs:

  • Initial Statement: You start on step 0 (ground floor)
  • Condition: Keep climbing while you're below step 5
  • Increment: Move up one step each time
  • Loop Body: What you do at each step

Your First For Loop: Printing Fruits

Let's write a simple for loop that prints numbers from 0 to 4:

for (Integer i = 0; i < 5; i++) {
    System.debug('Current number: ' + i);
}

Debug Output:

Current number: 0
Current number: 1
Current number: 2
Current number: 3
Current number: 4

Notice it stops at 4, not 5! That's because our condition is i < 5 (less than 5), not i <= 5 (less than or equal to 5).

Working with Lists and Index Positions

Here's where for loops get powerful. Lists in Apex are like numbered containers - the first item is at position 0, the second at position 1, and so on. We can use our loop counter to access each item:

List<String> fruits = new List<String>{'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'};
 
for (Integer i = 0; i < fruits.size(); i++) {
    System.debug('Fruit at position ' + i + ': ' + fruits[i]);
}

Debug Output:

Fruit at position 0: Apple
Fruit at position 1: Banana
Fruit at position 2: Cherry
Fruit at position 3: Date
Fruit at position 4: Elderberry

Understanding i++

You might be wondering: what does i++ actually mean? It's shorthand for i = i + 1. Here are equivalent ways to write it:

i++           // Increment by 1 (most common)
i = i + 1     // Same thing, more explicit
i += 1        // Also the same thing

You can also increment by different amounts:

for (Integer i = 0; i < 10; i += 2) {
    System.debug(i);  // Outputs: 0, 2, 4, 6, 8
}

Using list.size() for Dynamic Exit Conditions

Here's an important concept: always use list.size() as your exit condition when looping through lists. Why? Because lists can change size, and hardcoding numbers is dangerous.

// ❌ BAD: Hardcoded number
List<String> fruits = getFruits(); // Could have any number of items
for (Integer i = 0; i < 5; i++) {
    System.debug(fruits[i]); // Will crash if list has fewer than 5 items!
}
 
// ✅ GOOD: Dynamic size
List<String> fruits = getFruits();
for (Integer i = 0; i < fruits.size(); i++) {
    System.debug(fruits[i]); // Works with any size list
}

The fruits.size() method returns the number of items in the list. If your list has 3 items, size() returns 3. If it has 100 items, size() returns 100. This makes your code flexible and safe.

List Iteration: The Cleaner Alternative

Now let's look at list iteration (also called an "enhanced for loop" or "for-each loop"). This is a simpler syntax when you want to process every item in a list:

List<String> fruits = new List<String>{'Apple', 'Banana', 'Cherry'};
 
for (String fruit : fruits) {
    System.debug('I like ' + fruit);
}

Debug Output:

I like Apple
I like Banana
I like Cherry

How List Iteration Works

The syntax for (String fruit : fruits) means:

  • "For each String in the fruits list..."
  • "Create a variable called 'fruit' that holds the current item..."
  • "Run the code block..."
  • "Then move to the next item automatically"

Think of it like a conveyor belt at a grocery store checkout. The belt (your loop) automatically brings each item (fruit) to you one at a time. You don't need to know which position it's in - you just process whatever comes next.

List Iteration vs Traditional For Loop

These two loops do exactly the same thing:

List<String> colors = new List<String>{'Red', 'Green', 'Blue'};
 
// Traditional for loop
for (Integer i = 0; i < colors.size(); i++) {
    String color = colors[i];
    System.debug(color);
}
 
// List iteration (equivalent)
for (String color : colors) {
    System.debug(color);
}

Both output:

Red
Green
Blue

So which should you use? It depends on what you need!

When to Use Traditional vs List Iteration

Use List Iteration When:

✅ You need to process every item in the list ✅ You don't care about the index position ✅ You want cleaner, more readable code ✅ You're doing simple operations on each item

Example: Updating Account Names

List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 100];
 
for (Account acc : accounts) {
    acc.Name = acc.Name + ' - Updated';
}
update accounts;

Use Traditional For Loop When:

✅ You need to know the index position ✅ You want to process specific items (like every other one) ✅ You need to compare items at different positions ✅ You want to modify the list while looping (advanced) ✅ You're working with multiple lists simultaneously

Example: Processing Specific Positions

List<String> fruits = new List<String>{'Apple', 'Banana', 'Cherry', 'Date'};
 
// Only print fruits at even positions (0, 2, 4...)
for (Integer i = 0; i < fruits.size(); i += 2) {
    System.debug('Position ' + i + ': ' + fruits[i]);
}

Output:

Position 0: Apple
Position 2: Cherry

Debugging Your For Loops Like a Pro

When you're learning to code, debugging is your best friend. The System.debug() method lets you see what's happening inside your loops.

Basic Debugging Techniques

List<Integer> numbers = new List<Integer>{10, 20, 30, 40, 50};
 
for (Integer i = 0; i < numbers.size(); i++) {
    System.debug('=== Loop Iteration ===');
    System.debug('Index i: ' + i);
    System.debug('Value at this index: ' + numbers[i]);
    System.debug('Remaining items: ' + (numbers.size() - i - 1));
}

This produces clear output showing exactly what's happening at each step:

=== Loop Iteration ===
Index i: 0
Value at this index: 10
Remaining items: 4
=== Loop Iteration ===
Index i: 1
Value at this index: 20
Remaining items: 3
...

Debugging Common Loop Problems

Problem: Loop Not Running

List<String> emptyList = new List<String>();
 
for (Integer i = 0; i < emptyList.size(); i++) {
    System.debug('This never runs!');
}
 
System.debug('List size was: ' + emptyList.size()); // Shows 0

If your loop isn't running, check if the list is empty!

Problem: Infinite Loop

// ❌ DANGER: This never stops!
for (Integer i = 0; i < 10; /* MISSING INCREMENT! */) {
    System.debug('This runs forever!');
}

Always make sure your increment statement (i++) is present, or your loop will never end!

Problem: Off-by-One Errors

List<String> items = new List<String>{'A', 'B', 'C'};
 
// ❌ WRONG: Goes one too far
for (Integer i = 0; i <= items.size(); i++) { // Notice <=
    System.debug(items[i]); // CRASHES when i = 3
}
 
// ✅ CORRECT: Stops at the right place
for (Integer i = 0; i < items.size(); i++) { // Notice <
    System.debug(items[i]); // Works perfectly
}

Remember: lists use zero-based indexing. A list with 3 items has positions 0, 1, and 2 - not 1, 2, and 3!

Using Debug Logs to Track Variable Changes

Here's a practical debugging example that tracks how variables change:

List<Decimal> prices = new List<Decimal>{10.50, 20.00, 15.75};
Decimal total = 0;
 
for (Integer i = 0; i < prices.size(); i++) {
    System.debug('Before adding: total = ' + total);
    total += prices[i];
    System.debug('Added ' + prices[i] + ', new total = ' + total);
}
 
System.debug('Final total: ' + total);

Output:

Before adding: total = 0
Added 10.50, new total = 10.50
Before adding: total = 10.50
Added 20.00, new total = 30.50
Before adding: total = 30.50
Added 15.75, new total = 46.25
Final total: 46.25

This shows you exactly how your total builds up with each iteration.

Real-World Salesforce Examples

Let's apply these concepts to actual Salesforce scenarios you'll encounter.

Example 1: Finding High-Value Opportunities

// Query Opportunities from Salesforce
List<Opportunity> opps = [SELECT Id, Name, Amount
                          FROM Opportunity
                          WHERE StageName = 'Negotiation'
                          LIMIT 100];
 
List<Opportunity> highValueOpps = new List<Opportunity>();
 
// Use list iteration to filter
for (Opportunity opp : opps) {
    if (opp.Amount > 100000) {
        highValueOpps.add(opp);
        System.debug('High value opportunity: ' + opp.Name + ' ($' + opp.Amount + ')');
    }
}
 
System.debug('Found ' + highValueOpps.size() + ' high-value opportunities');

Example 2: Updating Accounts with Index Tracking

List<Account> accounts = [SELECT Id, Name, AnnualRevenue
                          FROM Account
                          WHERE Industry = 'Technology'
                          LIMIT 50];
 
// Use traditional for loop to track progress
for (Integer i = 0; i < accounts.size(); i++) {
    Account acc = accounts[i];
 
    // Update account
    acc.Description = 'Processed in batch #' + (i + 1);
 
    // Log progress every 10 accounts
    if (Math.mod(i + 1, 10) == 0) {
        System.debug('Progress: ' + (i + 1) + ' of ' + accounts.size() + ' accounts processed');
    }
}
 
update accounts;
System.debug('All ' + accounts.size() + ' accounts updated!');
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 10];
List<Contact> contactsToInsert = new List<Contact>();
 
// Create 3 contacts for each account
for (Account acc : accounts) {
    for (Integer i = 1; i <= 3; i++) {
        Contact con = new Contact(
            FirstName = 'Contact',
            LastName = '#' + i,
            AccountId = acc.Id,
            Email = 'contact' + i + '@' + acc.Name + '.com'
        );
        contactsToInsert.add(con);
    }
}
 
insert contactsToInsert;
System.debug('Created ' + contactsToInsert.size() + ' contacts for ' + accounts.size() + ' accounts');

This example shows nested loops - a loop inside another loop! The outer loop processes each Account, and the inner loop creates 3 Contacts for each one.

Documentation: Your Secret Weapon

Here's something many new developers don't realize: looking up syntax is completely normal and professional. Even experienced developers check documentation regularly!

Why Documentation Matters

  1. You Can't Memorize Everything: There are hundreds of methods and syntax rules in Apex
  2. Syntax Changes: Salesforce releases updates three times a year
  3. Examples Help: Official docs show real working code
  4. It Saves Time: 2 minutes reading docs beats 20 minutes debugging

Where to Find Apex Documentation

Official Salesforce Documentation:

  • Search "Apex Developer Guide" in Google
  • Go directly to developer.salesforce.com/docs
  • Look for "Apex Reference" and "Apex Developer Guide"

Salesforce Trailhead:

  • Free interactive tutorials
  • Hands-on challenges
  • Beginner-friendly explanations

Community Resources:

  • Salesforce Stack Exchange
  • Developer forums
  • YouTube tutorials

How to Use Documentation Effectively

When you need to implement a feature:

  1. Search for the concept: "Apex for loop list iteration"
  2. Read the syntax explanation: Understand the basic structure
  3. Study the examples: See it in action
  4. Adapt to your needs: Modify the example for your use case
  5. Test thoroughly: Run it and debug any issues

Example Search Process:

Let's say you forgot the exact syntax for list iteration:

  1. Search: "Apex enhanced for loop syntax"
  2. Find: Official documentation or a good tutorial
  3. See example: for (String item : myList)
  4. Apply: Replace String with your data type, item with your variable name, myList with your list
  5. Test: Run it and check the debug logs

Best Practices for For Loops

1. Use Descriptive Variable Names

// ❌ BAD: Unclear what 'x' represents
for (Integer x = 0; x < accounts.size(); x++) {
    System.debug(accounts[x]);
}
 
// ✅ GOOD: Clear index name
for (Integer accountIndex = 0; accountIndex < accounts.size(); accountIndex++) {
    System.debug(accounts[accountIndex]);
}
 
// ✅ EVEN BETTER: Use list iteration when you don't need the index
for (Account acc : accounts) {
    System.debug(acc);
}

2. Always Use Bulkified Patterns

In Salesforce, you need to think about governor limits - restrictions on how much code can run. Never do database operations inside loops!

// ❌ TERRIBLE: Database operation inside loop
for (Account acc : accounts) {
    update acc; // This will fail with more than 150 accounts!
}
 
// ✅ EXCELLENT: Collect all updates, then do one database operation
List<Account> accountsToUpdate = new List<Account>();
for (Account acc : accounts) {
    acc.Name = acc.Name + ' - Updated';
    accountsToUpdate.add(acc);
}
update accountsToUpdate; // Single database operation

3. Check for Empty Lists Before Looping

List<Contact> contacts = [SELECT Id FROM Contact WHERE LastName = 'Smith'];
 
// ✅ GOOD: Check before processing
if (!contacts.isEmpty()) {
    for (Contact con : contacts) {
        // Process contact
    }
} else {
    System.debug('No contacts found');
}

4. Use Clear Exit Conditions

// ❌ CONFUSING: Magic number
for (Integer i = 0; i < 47; i++) {
    // Where did 47 come from?
}
 
// ✅ CLEAR: Named constant or list size
Integer MAX_RECORDS = 47;
for (Integer i = 0; i < MAX_RECORDS; i++) {
    // Clear what this represents
}
 
// ✅ EVEN BETTER: Use the actual list size
for (Integer i = 0; i < myList.size(); i++) {
    // Dynamically adjusts to list size
}

5. Add Debug Statements During Development

for (Integer i = 0; i < products.size(); i++) {
    System.debug('Processing product ' + (i + 1) + ' of ' + products.size());
    // Your actual code here
}

You can remove these debug statements later, but they're invaluable when learning and troubleshooting.

Common Mistakes to Avoid

Mistake #1: Forgetting the Increment

// ❌ INFINITE LOOP: Never stops!
for (Integer i = 0; i < 10;) {
    System.debug(i); // Prints 0 forever
}
 
// ✅ CORRECT: Includes increment
for (Integer i = 0; i < 10; i++) {
    System.debug(i); // Counts 0-9 then stops
}

Mistake #2: Wrong Comparison Operator

List<String> items = new List<String>{'A', 'B', 'C'};
 
// ❌ WRONG: Uses <= instead of <
for (Integer i = 0; i <= items.size(); i++) {
    System.debug(items[i]); // CRASHES when i = 3
}
 
// ✅ CORRECT: Uses < for zero-based lists
for (Integer i = 0; i < items.size(); i++) {
    System.debug(items[i]); // Works perfectly
}

Mistake #3: Modifying List During Iteration

List<Integer> numbers = new List<Integer>{1, 2, 3, 4, 5};
 
// ❌ DANGEROUS: Modifying the list you're iterating
for (Integer num : numbers) {
    if (num == 3) {
        numbers.remove(2); // BAD! Can cause unpredictable behavior
    }
}
 
// ✅ SAFE: Create a new list for removals
List<Integer> numbersToKeep = new List<Integer>();
for (Integer num : numbers) {
    if (num != 3) {
        numbersToKeep.add(num);
    }
}
numbers = numbersToKeep;

Mistake #4: Not Understanding Zero-Based Indexing

List<String> fruits = new List<String>{'Apple', 'Banana', 'Cherry'};
 
// ❌ WRONG THINKING: "First item is at position 1"
System.debug(fruits[1]); // Outputs "Banana" (the SECOND item!)
 
// ✅ CORRECT: "First item is at position 0"
System.debug(fruits[0]); // Outputs "Apple" (the FIRST item)

Mistake #5: Using the Wrong Loop Type

List<Account> accounts = [SELECT Id, Name FROM Account];
 
// ❌ UNNECESSARY: Traditional loop when you don't need the index
for (Integer i = 0; i < accounts.size(); i++) {
    Account acc = accounts[i];
    System.debug(acc.Name);
}
 
// ✅ BETTER: List iteration for simple processing
for (Account acc : accounts) {
    System.debug(acc.Name);
}

Mistake #6: Database Operations Inside Loops

List<Contact> contacts = [SELECT Id, AccountId FROM Contact LIMIT 200];
 
// ❌ TERRIBLE: Queries inside a loop (hits governor limits!)
for (Contact con : contacts) {
    Account acc = [SELECT Name FROM Account WHERE Id = :con.AccountId];
    System.debug(acc.Name);
}
 
// ✅ EXCELLENT: Query once, use a Map
Map<Id, Account> accountMap = new Map<Id, Account>(
    [SELECT Id, Name FROM Account WHERE Id IN :contactAccountIds]
);
 
for (Contact con : contacts) {
    Account acc = accountMap.get(con.AccountId);
    System.debug(acc.Name);
}

Conclusion

Congratulations! You've just learned one of the most fundamental skills in Salesforce development. For loops are the building blocks you'll use every single day - to process records, calculate totals, validate data, and build amazing solutions.

Key Takeaways

For loops repeat code - perfect for processing lists of data ✅ Two main types: Traditional (with index) and List Iteration (without index) ✅ Traditional for loops give you control and position tracking ✅ List iteration is cleaner when you don't need the index ✅ System.debug() is your best friend for understanding what's happening ✅ Documentation is normal - even experts look things up! ✅ Bulkify your code - never put database operations inside loops

Your Next Steps

Now that you understand for loops, you're ready to:

  1. Practice on Lightning Challenge - Apply these concepts to real coding challenges
  2. Explore nested loops - Loops inside loops for complex data structures
  3. Learn about governor limits - Understanding why bulkification matters
  4. Study SOQL for loops - A special type of loop for large data sets
  5. Master Collections - Lists, Sets, and Maps work hand-in-hand with loops

Remember: every expert developer started exactly where you are now. The key is practice, patience, and persistence. Don't be afraid to experiment, make mistakes, and learn from them. That's how you grow!

Keep coding, keep learning, and most importantly - don't hesitate to check the documentation. It's not cheating; it's how professionals work. Happy coding!

WW

About Warren Walters

Salesforce MVP and transformative mentor with 8+ years in the Salesforce realm. Founder of Lightning Challenge, dedicated to nurturing the next generation of Salesforce talent through hands-on practice and real-world coding challenges.

Visit Profile →
Share:

Related Posts