Salesforce Apex: Why Best Practices Matter
Whether you're a seasoned developer or a Salesforce admin just getting your hands dirty with code, you've probably come across the term "best practices." But what does it actually mean, and why is it so important—especially when working with Apex?
In the Salesforce ecosystem, best practices refer to tried-and-true techniques that are widely recognized as effective and reliable ways to write, structure, and maintain your code. These aren’t just helpful suggestions—they’re often the difference between scalable, high-performing applications and ones that break under pressure (or raise eyebrows during code reviews).
In this blog post, we’ll explore key Apex best practices—from bulkifying your code and avoiding SOQL/DML in loops, to enforcing security rules and handling exceptions correctly. We'll dive into why these practices are considered "best," and even ask the tough question: are there ever situations where it makes sense to bend or break the rules?
Follow a Consistent Naming Convention
Naming conventions often spark debate among development teams, but their value is undeniable—especially when everyone sticks to them. A consistent naming strategy makes it significantly easier for team members to understand and navigate the org, especially when revisiting code or collaborating on projects.
The exact rules of your naming convention can vary based on your team’s preferences or project needs, but the key is consistency. Adopting and adhering to a clear naming convention reduces confusion, simplifies maintenance, and minimizes the time spent deciphering someone else’s logic—allowing you to focus more on building great solutions.
Avoid Nested Loops
Using a loop inside another loop—nested loops—is sometimes necessary, especially when working with related data sets. On the surface, it might not seem like a problem. The code might run smoothly without hitting performance bottlenecks or governor limits. However, the genuine concern lies in readability and maintainability.
Every time you nest a loop, you're increasing the cognitive complexity of your code—the mental effort required to understand it. What may seem straightforward today could become difficult to decipher later, especially for another developer (or even your future self). Debugging or making changes becomes more time-consuming and error-prone.
A better approach is to refactor the inner logic into separate, well-named methods. This improves the overall readability of your code by allowing others to understand the high-level flow without getting bogged down in the details. It also promotes modularity and testability, making your codebase more straightforward to maintain and evolve.
Bulkify Your Apex Code
In simple terms, bulkification means ensuring your code can process multiple records at once—not just a single record. This is especially important in scenarios like triggers, where a batch of up to 200 records can be processed in a single transaction.
Why Bulkification Matters
In Salesforce, Apex is subject to strict governor limits that restrict how many times your code can perform operations like SOQL queries or DML statements. If your code processes each record individually (e.g., inserting a contact inside a loop), you can quickly exceed those limits—leading to errors or failed transactions.
Here's a bad example of non-bulkified code:
This approach runs a separate DML operation for each account—up to 200 individual insert calls in a single trigger execution!
Now compare that to the bulkified version:
By collecting all contacts into a list and inserting them at once, we reduce the number of DML statements from 200 to 1—a major performance and reliability boost.
Not Just for Triggers
Bulkification isn’t limited to triggers. Any Apex code that processes records—such as batch jobs, invocable methods, or API handlers—should be built with bulk handling in mind. Using collections (Lists, Maps, Sets) and performing batch operations helps make your code scalable and governor-limit-safe.
When Not to Bulkify?
In rare cases—such as when a method is guaranteed to handle only one record, like a Screen Flow action or single-record API call—you might choose not to bulkify. But in general, the overhead of bulkifying is minimal, and the habit will protect you from unexpected scale issues down the road.
Avoid SOQL Queries and DML Statements Inside Loops
One of the most common and dangerous pitfalls in Apex development is placing SOQL queries or DML operations inside a for loop. It might seem harmless at first glance—especially when dealing with just a few records—this practice can cause serious performance issues and even break your code completely once you scale.
Why This Is a Problem
Salesforce enforces governor limits to ensure shared resources are used efficiently. Two of the most critical limits are:
-
Maximum 100 SOQL queries per Apex transaction
-
Maximum 150 DML operations (like insert, update, delete, etc.)
If your code runs a query or a DML operation inside a loop, and that loop iterates over 100+ records, you’ll hit these limits fast—and your transaction will fail with a governor limit exception.
Bad Example: SOQL and DML Inside Loop
This code:
-
Performs a SOQL query for each Account (up to 200 per trigger execution)
-
Performs an update DML operation for each Contact individually. That’s a one-way ticket to hitting your limits—and breaking your application.
Good Example: Bulkified SOQL and DML
This version:
-
Uses one SOQL query for all accounts
-
Performs one DML update for all modified contacts. It’s efficient, scalable, and well within governor limits—no matter how many records you’re processing.
Quick Rule of Thumb
If you see a SOQL query or a DML statement inside a loop—stop and refactor. Move the operation outside the loop, gather your data in collections, and process it in bulk.
Avoid Hard-Coded IDs in Apex Code
While it might seem like a convenient shortcut during development, it introduces significant risks, especially when moving code between environments like sandboxes, scratch orgs, and production.
Why Hard-Coding IDs Is a Bad Idea
Hardcoded IDs work perfectly in your sandbox, but they will likely fail when you deploy your code to a different environment. That’s because Salesforce generates unique IDs for each record in each org. Even if you create the same object or record type in production, its ID won’t match the one in your sandbox.
A typical example is Record Type IDs. If you write something like:
This code will break the moment it's deployed to another environment where the Record Type ID is different.
Better Alternatives
1. Use Schema Methods for Record Types
Instead of hardcoding, retrieve the Record Type ID dynamically using the Schema class:
This approach works across all environments as long as the DeveloperName of the record type ('Business Account' in this example) remains the same—which it should!
2. Use SOQL Queries to Fetch IDs
For records like specific Accounts, Profiles, or Custom Settings, use SOQL queries to fetch the correct ID dynamically:
This ensures the code is portable, dynamic, and resilient to environment changes.
3. Use Custom Metadata or Custom Settings
For truly environment-specific values—like integration user credentials, special Account IDs, or default settings—store them in Custom Metadata Types or Custom Settings. This allows you to manage those values declaratively and change them per environment without modifying the codebase.
This approach gives you flexibility, ease of maintenance, and the ability to adapt as your org evolves.
Write Effective Test Classes for High Code Coverage
Writing unit tests is a key responsibility for developers to ensure that Apex code behaves as expected under various conditions. Rather than focusing solely on hitting coverage thresholds, follow these best practices to create meaningful and maintainable test classes:
- Test with bulk records to simulate real-world scenarios and ensure your code handles large datasets correctly.
- Include both positive and negative test cases to verify that your logic works as intended—and also fails gracefully when needed.
- Perform restricted user testing to confirm that your code respects sharing rules and user permissions.
- Aim for one assert statement per test method to verify outcomes and make it easier to pinpoint failures clearly.
- Avoid using @isTest(seeAllData=true)—instead, create your own test data to ensure test reliability and data independence.
By following these principles, you’ll not only meet Salesforce’s deployment requirements but also build a solid foundation for long-term code quality and system stability.
Final Thoughts
Writing efficient, scalable, and maintainable Apex code isn’t just about getting things to work — it’s about building solutions that are robust, adaptable, and easy to understand long after they’re written.
These best practices aren’t just theoretical guidelines — they’re practical habits that lead to cleaner, more reliable code. Whether you're deploying to production, collaborating with a team, or revisiting your own work months later, following these principles will save time, reduce bugs, and ultimately help you build better Salesforce applications.
Start small, stay consistent, and make these practices part of your everyday development workflow. Your future self — and your team — will thank you.