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.
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:
- Initial Statement (
Integer i = 0): Creates a counter variable and sets its starting value - Condition (
i < 5): The loop continues while this is true - 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 thingYou 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 0If 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!');Example 3: Creating Related Records
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
- You Can't Memorize Everything: There are hundreds of methods and syntax rules in Apex
- Syntax Changes: Salesforce releases updates three times a year
- Examples Help: Official docs show real working code
- 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:
- Search for the concept: "Apex for loop list iteration"
- Read the syntax explanation: Understand the basic structure
- Study the examples: See it in action
- Adapt to your needs: Modify the example for your use case
- Test thoroughly: Run it and debug any issues
Example Search Process:
Let's say you forgot the exact syntax for list iteration:
- Search: "Apex enhanced for loop syntax"
- Find: Official documentation or a good tutorial
- See example:
for (String item : myList) - Apply: Replace
Stringwith your data type,itemwith your variable name,myListwith your list - 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 operation3. 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:
- Practice on Lightning Challenge - Apply these concepts to real coding challenges
- Explore nested loops - Loops inside loops for complex data structures
- Learn about governor limits - Understanding why bulkification matters
- Study SOQL for loops - A special type of loop for large data sets
- 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!
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 →