ApexArena ›
Problems ›
Apex Class & Business Logic Practice Problems
Apex Class
Apex Class & Business Logic Practice Problems
81 free Salesforce Apex challenges · Full problem statements, best-practice notes, and step-by-step hints · Solve live on ApexArena
This guide walks through 81 general-purpose Apex classes — collections, string handling, business logic, and service-layer design. Each entry below includes the full problem statement, the governor-limit and best-practice constraints it's testing, and a numbered approach to solving it — then you can open the same problem in ApexArena's browser-based Apex editor and get instant pass/fail feedback against real test cases.
Create a utility class TemperatureConverter with two static methods:
celsiusToFahrenheit(Decimal c) → returns Decimal
fahrenheitToCelsius(Decimal f) → returns Decimal
Formulas: F = C × 9/5 + 32 | C = (F − 32) × 5/9
Constraints
Handle null input — return null if input is null
Round to 2 decimal places
Approach
1Use Decimal.setScale(2, RoundingMode.HALF_UP) or .setScale(2) to round.
2Guard against null: if (c == null) return null;
EasyApex Class
2. Opportunity Stage Helper
Problem #23 · Salesforce Apex Coding Challenge
Problem Statement
Create a class OpportunityHelper with a static method
isWon(String stage) that returns true if the stage equals
"Closed Won" (case-insensitive), otherwise false.
Also add isLost(String stage) that returns true for "Closed Lost".
Approach
1Use equalsIgnoreCase() for case-insensitive comparison.
2Guard null: if (stage == null) return false;
EasyApex Class
3. String Utilities Class
Problem #26 · Salesforce Apex Coding Challenge
Problem Statement
Create a class StringUtils with these static methods:
isPalindrome(String s) — returns true if s reads the same forwards and backwards (ignore case)
countWords(String s) — returns the number of words (split on whitespace)
capitalizeFirst(String s) — capitalizes the first letter of every word
Approach
1For palindrome: reverse the string with new StringBuilder(s).reverse().toString() or reverse the chars manually.
2For words: s.split('\\s+').size() after trimming.
3For capitalize: s.split(' '), capitalize each word, String.join.
EasyApex Class
4. Math Helper Class
Problem #29 · Salesforce Apex Coding Challenge
Problem Statement
Create a class MathHelper with these static methods:
factorial(Integer n) — returns n! (use recursion or iteration)
isPrime(Integer n) — returns true if n is prime
fibonacci(Integer n) — returns the nth Fibonacci number (0-indexed)
Constraints
Handle n ≤ 0 gracefully (return 0 for fibonacci, 1 for factorial, false for isPrime)
Approach
1Factorial: for (Long result = 1; n > 1; n--) result *= n;
2isPrime: check divisibility from 2 to sqrt(n).
3Fibonacci: iterative with two variables is simplest.
MediumApex ClassSOQL
5. Account Service Class
Problem #32 · Salesforce Apex Coding Challenge
Problem Statement
Create a class AccountService with:
getHotAccounts() — returns a List<Account> where Rating = 'Hot', ordered by Name
deactivateSmallAccounts(Decimal maxRevenue) — sets Active__c = false on all Accounts with AnnualRevenue < maxRevenue, updates them, returns the count updated
Approach
1Use WHERE Rating = 'Hot' ORDER BY Name in your SOQL.
2Collect records to update in a List, then call update once outside the loop.
EasyApex Class
6. Tiered Discount Calculator
Problem #34 · Salesforce Apex Coding Challenge
Problem Statement
Create a class DiscountCalculator with a static method
getDiscountedPrice(Decimal originalPrice, Integer quantity):
Quantity 1–9 → 0% discount
Quantity 10–49 → 10% discount
Quantity 50–99 → 20% discount
Quantity ≥ 100 → 30% discount
Returns the final price per unit after discount, rounded to 2 decimal places.
Return true only for explicitly listed transitions
Staying in the same stage returns false
Null inputs return false
Approach
1Use a Map> to store allowed next stages for each current stage
2Check containsKey before calling get to avoid null pointer
3Use List.contains() to test if toStage is in the allowed set
EasyApex Classes
16. Territory Region Mapper
Problem #72 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class TerritoryMapper with a static method getRegion
that maps US state codes to sales regions.
Region mappings:
Northeast: ME, NH, VT, MA, RI, CT, NY, NJ, PA
Southeast: DE, MD, VA, WV, NC, SC, GA, FL, AL, MS, TN, KY, AR, LA
Midwest: OH, IN, IL, MI, WI, MN, IA, MO, ND, SD, NE, KS
Southwest: TX, OK, NM, AZ
West: CO, WY, MT, ID, WA, OR, CA, NV, UT, AK, HI
Unknown state codes return 'Unknown'
Method Signature
static String getRegion(String stateCode)
Constraints
State codes are uppercase 2-letter abbreviations
Null or blank input returns 'Unknown'
Case-insensitive matching (normalize to uppercase)
Approach
1Use a Map where each state code maps to a region name
2Alternatively, use a Set for each region and check containment
3Remember to call toUpperCase() to normalize the input
HardAPIApex Classes
17. Apex REST Account Service
Problem #73 · Salesforce Apex Coding Challenge
Problem Statement
Create an Apex REST service that exposes Account data via HTTP endpoints.
Requirements:
Annotate the class with @RestResource(urlMapping='/accounts/*')
Implement a GET method annotated with @HttpGet that queries Accounts
and returns them as a JSON-serializable list
Implement a POST method annotated with @HttpPost that accepts an
Account name in the request body and inserts a new Account
Implement a DELETE method annotated with @HttpDelete that deletes
an Account by ID from the URL parameter
Use RestContext.request and RestContext.response where needed
Return appropriate HTTP status codes (200, 201, 400, 404)
Constraints
All methods must be global static
Handle the case where the record is not found (404)
Use try/catch for DML operations
Approach
1Use RestContext.request.requestURI to extract the record ID from the URL
2Set RestContext.response.statusCode = 201 after a successful insert
3Deserialize the POST body with JSON.deserialize() or RestContext.request.requestBody.toString()
HardAPIApex Classes
18. HTTP Callout Handler with Mock Support
Problem #74 · Salesforce Apex Coding Challenge
Problem Statement
Build a robust HTTP callout handler class and its corresponding mock for testing.
Requirements for HttpCalloutHandler:
A method fetchExternalData(String endpoint) that makes an HTTP GET callout
Parse the JSON response into a wrapper class ApiResponse with fields:
Integer statusCode, String body, Boolean success
If status code is 200, set success = true; otherwise false
Handle callout exceptions — set success=false, statusCode=500, body=exception message
Requirements for HttpCalloutHandlerMock:
Implement the HttpCalloutMock interface
Implement the respond method returning a mock 200 response
Constraints
Use Http, HttpRequest, HttpResponse classes
Set request timeout to 10000ms (10 seconds)
Approach
1Use new Http().send(req) to make the callout
2Set req.setMethod('GET') and req.setEndpoint(endpoint)
3req.setTimeout(10000) sets the timeout in milliseconds
4For the mock, return a new HttpResponse with setStatusCode(200) and setBody('{"ok":true}')
MediumApex Classes
19. Custom Exception Hierarchy
Problem #76 · Salesforce Apex Coding Challenge
Problem Statement
Design a robust custom exception hierarchy for a payment processing service.
Requirements:
Create a base exception PaymentException extending Exception
Create subclasses:
InsufficientFundsException — includes Decimal balance and Decimal required fields
InvalidCardException — includes String cardType field
PaymentGatewayException — includes Integer errorCode and String gatewayMessage
Write a PaymentProcessor class with method processPayment(Decimal amount, String cardType, Decimal accountBalance)
Throw InvalidCardException if cardType is not in ('Visa','Mastercard','Amex')
Throw InsufficientFundsException if accountBalance < amount
Otherwise return true
Constraints
Each custom exception must extend the appropriate parent
Custom fields must be set via constructor or setter before throwing
Approach
1Use a Set of valid card types and check .contains(cardType)
2Throw and set fields: InvalidCardException e = new InvalidCardException(); e.cardType = cardType; throw e;
3Order matters: check card type before balance to match expected exception priority
EasyApex ClassesStrings
20. String Utility: Reverse Words in a Sentence
Problem #87 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class StringUtils with a static method
reverseWords(String sentence) that reverses the order of words
in a sentence while preserving each word's characters.
Examples
"Hello World Apex" → "Apex World Hello"
"I love Salesforce" → "Salesforce love I"
null or blank → ""
Method Signature
public static String reverseWords(String sentence)
Approach
1Use String.split(' ') to break into words.
2Reverse the list with a simple loop or by building a new list.
3Rejoin with String.join(list, ' ').
EasyApex ClassesCollections
21. Word Frequency Counter
Problem #88 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class WordCounter with a static method
countWords(List<String> words) that returns a
Map<String, Integer> counting the frequency of each word
(case-insensitive).
Examples
['apex','java','apex','APEX'] → {apex:3, java:1}
Empty list → empty map
Method Signature
public static Map<String, Integer> countWords(List<String> words)
Approach
1Normalise each word with .toLowerCase() before counting.
2Use map.containsKey() to check existing entries.
3Increment with map.get(word) + 1.
EasyApex ClassesRecursion
22. Factorial Calculator
Problem #89 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class MathUtils with a static method
factorial(Integer n) that returns n!.
Rules
0! = 1 and 1! = 1
5! = 120
Return -1 for negative inputs
Method Signature
public static Integer factorial(Integer n)
Approach
10! = 1 is the base case.
2Use a loop: result *= i for i from 2 to n.
3Or use recursion: n * factorial(n-1).
EasyApex ClassesControl Flow
23. FizzBuzz: Apex Edition
Problem #90 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class FizzBuzz with a static method
generate(Integer n) that returns a List<String>
of 1 through n with:
Multiples of 3 → "Fizz"
Multiples of 5 → "Buzz"
Multiples of both → "FizzBuzz"
Otherwise → the number as a String
Method Signature
public static List<String> generate(Integer n)
Approach
1Check divisibility with Math.mod(i, 3) == 0.
2Check FizzBuzz first (divisible by both 3 and 5).
Your sales operations team needs to automatically classify accounts into revenue tiers
to determine service level agreements and pricing models.
Write an Apex class AccountTierService with a static method
getRevenueTier(Decimal annualRevenue) that returns a tier label based on the account's annual revenue.
Tier Definitions
Annual Revenue
Tier
< $50,000 or null
'Bronze'
$50,000 – $249,999
'Silver'
$250,000 – $999,999
'Gold'
≥ $1,000,000
'Platinum'
Method Signature
public static String getRevenueTier(Decimal annualRevenue)
3Boundary values: 50,000 is the first Silver value; 250,000 is the first Gold value.
EasyApex ClassesBusiness Logic
25. DiscountApprovalService: Route Discount Approval by Level
Problem #146 · Salesforce Apex Coding Challenge
Problem Statement
Your finance team requires that discount approvals are routed to the correct authority
level based on the percentage discount being offered on an opportunity.
Write an Apex class DiscountApprovalService with a static method
getApprovalLevel(Decimal discountPercent) that returns the required approver.
Approval Matrix
Discount %
Approval Level
null or < 0
'Invalid'
0% – 5%
'Rep'
>5% – 15%
'Manager'
>15% – 25%
'Director'
>25%
'VP'
Method Signature
public static String getApprovalLevel(Decimal discountPercent)
Approach
1Check null and negative values first — return "Invalid".
3Use <= 5 for the Rep boundary so exactly 5% still routes to Rep.
MediumApex ClassesBusiness LogicScoring
26. AccountHealthService: Calculate Multi-Factor Health Score
Problem #147 · Salesforce Apex Coding Challenge
Problem Statement
Your account management team needs a single numeric health score (0–100) per account
to prioritise outreach. The score is calculated from five weighted factors.
Write an Apex class AccountHealthService with a static method
getHealthScore(String industry, Decimal annualRevenue, String rating,
Integer contactCount, String billingState) that returns an Integer score.
1Start with revenue: if null → 0 pts; >= 1M → 30; >= 500K → 20; >= 100K → 10.
2For industry, always award at least 10 pts; bump to 20 for Technology or Finance.
3Guard contactCount and billingState against null before comparing.
4Maximum possible score is 30 + 25 + 20 + 15 + 10 = 100.
EasyApex Class
27. Apex Class: Lead Qualifier — Grade by Employees & Budget
Problem #280 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class LeadQualifier with a static method
qualify(String source, Integer numEmployees, Decimal budget)
that returns a letter grade based on company size and budget.
Grading Rules
A — employees ≥ 1,000 OR budget ≥ 500,000
B — employees ≥ 200 OR budget ≥ 100,000
C — employees ≥ 50
Unqualified — everything else
Requirements
Null-safe: treat null employees or budget as 0
Define thresholds as named constants (not magic numbers)
Use with sharing on the class
Approach
1Treat null numEmployees and budget as 0 using the ternary operator.
2Check the highest grade first (A), then B, C — use early returns.
3Store thresholds in private static final constants to avoid magic numbers.
Write an Apex class InvoiceCalculator with a static method
calculateTotal(List<Decimal> lineItems, Decimal taxRate, Decimal discountPct)
that computes the final invoice total.
Formula
subtotal = sum of all positive line items
afterDiscount = subtotal × (1 − discountPct ÷ 100)
total = afterDiscount × (1 + taxRate ÷ 100)
Return total rounded to 2 decimal places.
Requirements
Skip null or negative line items
Treat null taxRate or discountPct as 0
Return 0 for null or empty line-item list
Round the result to 2 decimal places using .setScale(2)
4Use .setScale(2) on a Decimal to round to 2 decimal places.
EasyApex Class
29. Apex Class: Phone Formatter — US Format (XXX) XXX-XXXX
Problem #282 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class PhoneFormatter with a static method
formatPhone(String rawPhone) that formats any 10-digit US phone
number into the standard (XXX) XXX-XXXX pattern.
Rules
Strip all non-digit characters first (dashes, dots, spaces, parens)
If the result is exactly 10 digits, format as (XXX) XXX-XXXX
If not 10 digits, return the original input unchanged
If blank or null, return ""
Examples
"415-555-1234" → "(415) 555-1234"
"4155551234" → "(415) 555-1234"
"415.555.1234" → "(415) 555-1234"
"12345" → "12345" (not 10 digits)
Approach
1Use String.replaceAll('[^0-9]', '') to strip all non-digit characters.
2String.substring(start, end) extracts a portion of the string (end is exclusive).
3If digits.length() != 10, return the original rawPhone unchanged.
EasyApex Class
30. Apex Class: SLA Checker — Is Case SLA Breached?
Problem #283 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class SLAChecker with a static method
isBreached(String priority, Integer hoursOpen) that returns
true when a case has exceeded its SLA time limit.
SLA Thresholds
Priority
Max Hours
Critical
2
High
4
Medium
8
Low
24
Requirements
Return false for null/blank priority or null hoursOpen
Return false for unknown priority values
Store thresholds in a Map constant — no if-else chains
2Use map.get(priority) — if it returns null, the priority is unknown.
3Guard against null inputs before doing any comparison.
EasyApex Class
31. Apex Class: Name Formatter — Salutation + Full Name
Problem #284 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class NameFormatter with two static methods:
Method 1 — fullName
public static String fullName(String salutation, String firstName, String lastName)
Concatenates non-blank parts separated by a single space.
e.g. fullName("Mr.", "John", "Smith") → "Mr. John Smith"
e.g. fullName(null, "John", "Smith") → "John Smith"
Method 2 — lastFirst
public static String lastFirst(String firstName, String lastName)
Returns "Last, First" format.
e.g. lastFirst("John", "Smith") → "Smith, John"
e.g. lastFirst(null, "Smith") → "Smith"
Requirements
Skip blank/null parts — do not produce leading/trailing spaces
Use String.isBlank() for null-safe blank checks
Approach
1Build a List, add only non-blank parts, then String.join(list, ' ').
2String.isBlank() returns true for null, empty, and whitespace-only strings.
3For lastFirst, handle each missing-part edge case separately.
MediumApex Classes
32. Apex Service Class: Merge Duplicate Contacts by Account
Problem #357 · Salesforce Apex Coding Challenge
Problem Statement
Write a service class ContactMergeService with a static method
mergeContactsByAccount(Id accountId) that merges duplicate Contacts
under the same Account.
Two Contacts are duplicates when they share the same full name (case-insensitive).
The oldest Contact (earliest CreatedDate) is kept as the master record.
Requirements
public with sharing class.
Throw IllegalArgumentException when accountId is null.
Query Contacts ordered by CreatedDate ASC; group by full name (case-insensitive).
Call Database.merge(master, List<Id>) with up to 2 IDs per call.
System.debug the merged count — visible in the Output tab.
1For each signal, run an aggregate SOQL query with GROUP BY AccountId against the relevant object, and collect the AccountIds returned into a Set — that Set membership is your true/false flag for that signal.
2Clamp: score = Math.max(0, Math.min(100, score)); — ensures the result is always in [0, 100].
4Use System.debug to print the score so it shows in the Console/Output tab after running.
EasyApex Class
34. Calculate Customer Loyalty Points on Closed Won
Problem #455 · Salesforce Apex Coding Challenge
Business Scenario
Marketing wants every customer's Account to accumulate loyalty points
whenever one of their Opportunities closes as Closed Won. Points are earned
at a rate of 1 point per $100 of the won Amount, stored on a custom field
Loyalty_Points__c (Number) on Account.
Requirements
Build a reusable service method awardPoints(List<Opportunity> closedWonOpps)
that a trigger can call with the Opportunities that just became Closed Won.
Aggregate points per Account in memory first — one Opportunity may not be the only
one closing for that Account in the same transaction.
Query the affected Accounts in a single SOQL call and update all of them in a single
DML call.
Best Practices
No SOQL or DML inside a loop — collect Account Ids into a Set first, then
query and update once outside any loop.
WITH SECURITY_ENFORCED and a LIMIT on the query respect the
running user's field/object-level security and cap the result size.
Wrap the update in try/catch(DmlException) so a partial failure never
throws an unhandled exception back to the caller.
Approach
1Guard against null AccountId and null Amount before doing any math.
2Use a Map keyed by AccountId to sum points before touching the database at all.
3Query once with WHERE Id IN :accountIds, then loop the query results (not the opportunities) to build the update list.
EasyApex Class
35. Determine Customer Discount by Account Type and Revenue
Problem #456 · Salesforce Apex Coding Challenge
Business Scenario
Sales wants a single, reusable place to compute the discount percentage a customer
qualifies for, based on their Account.Type (standard picklist values such as
Technology Partner and Reseller count as "partner" accounts) and
AnnualRevenue.
Requirements
Partner accounts (Technology Partner or Reseller) with
revenue ≥ 1,000,000 get 20%; ≥ 250,000 get 15%; otherwise 10%.
Non-partner accounts with revenue ≥ 1,000,000 get 10%; ≥ 250,000 get 5%; otherwise 0%.
Null AnnualRevenue must be treated as 0, never throw an error.
Best Practices
Use named constants for the revenue thresholds instead of bare numbers, so tuning the
business rule later doesn't require hunting through comparison logic.
Check the highest-value tiers first with early returns to keep the logic easy to read
and avoid overlapping conditions.
Approach
1Treat a null annualRevenue as 0 with a ternary before any comparison.
2isPartner should be true for both 'Technology Partner' and 'Reseller'.
3Order your checks from the highest discount down to the lowest, using early returns.
MediumApex Class
36. Validate Product Stock Before Creating an Order
Problem #457 · Salesforce Apex Coding Challenge
Business Scenario
Operations wants Orders blocked before they're created if the requested quantity
exceeds available inventory. Orders live on a custom object Order__c with a
lookup Product__c to Product2 and a Quantity__c
number field. Product2 has a custom Stock_Quantity__c field
tracking units on hand.
Requirements
Build validateStock(List<Order__c> ordersToInsert) that throws a
custom exception when any product's total requested quantity across the batch exceeds
its available stock.
If the same product appears on multiple orders in the same batch, their quantities
must be summed before comparing against stock.
Look up current stock for every referenced product in a single query.
Best Practices
No SOQL inside a loop — gather all Product Ids first, then query stock once.
A custom Exception subclass communicates the specific failure reason
clearly to the caller (e.g. a trigger) instead of a generic error.
WITH SECURITY_ENFORCED and a LIMIT protect the query and
respect field-level security on Stock_Quantity__c.
Approach
1Build a Map that sums Quantity__c per Product__c before querying anything.
2Use Map constructed directly from the SOQL query for O(1) lookups by Id.
3Compare the summed requested quantity against Stock_Quantity__c and throw your custom exception with a clear message.
MediumApex Class
37. Create Utility Methods to Calculate Shipping Charges by Region
Problem #458 · Salesforce Apex Coding Challenge
Business Scenario
Fulfillment needs a reusable utility to compute shipping charges consistently across the
org. Orders live on the custom object Order__c, which carries
Ship_Region__c (Text), Ship_Country__c (Text), and
Weight_Kg__c (Number).
Requirements
calculateShippingCharge(region, country, weightKg) returns a base rate
(domestic USA vs. international) plus a per-kilogram charge, plus a surcharge for
designated remote regions (Alaska, Hawaii, Remote Islands).
Provide a bulk-friendly wrapper calculateShippingChargesForOrders(List<Order__c>)
that returns a Map<Id, Decimal> of Order Id to computed charge.
Treat missing weight or a null/blank country as safe defaults rather than throwing.
Best Practices
All rates and the remote-region list are named constants, so Finance can adjust the
pricing model in one place.
The bulk method loops purely in memory (no SOQL/DML at all) — it is safe to call from
inside a trigger for up to 200 records without any governor-limit risk.
Approach
1Use a Set of remote region names so the "is remote" check is a simple .contains() call.
2country == "USA" (or null, defaulting to domestic) determines the base rate.
3The bulk method should just call the single-record method in a loop — no separate logic to duplicate.
MediumApex Class
38. Generate Monthly Sales Summaries for Management Dashboards
Problem #459 · Salesforce Apex Coding Challenge
Business Scenario
Management wants a dashboard-ready breakdown of Closed Won Opportunities by calendar
month for a given date range — total count and total dollar amount per month, sorted
chronologically.
Requirements
Build getMonthlySummaries(Date startDate, Date endDate) returning a
list of MonthSummary inner-class instances (month label, won count, total
amount), sorted oldest month first.
Only include Opportunities where StageName = 'Closed Won' and
CloseDate falls within the given range (inclusive).
Return an empty list for an invalid range (null dates or start after end) instead of
throwing.
Best Practices
A single SOQL query pulls all relevant Opportunities; monthly grouping is done in
memory with a Map, avoiding any per-month query.
WITH SECURITY_ENFORCED and a LIMIT keep the query safe and
bounded for a reporting-style read.
Sorting the month keys (formatted as YYYY-MM) guarantees a stable,
chronological order regardless of the order records come back in.
Approach
1Build the month key as opp.CloseDate.year() + "-" + a zero-padded month, e.g. leftPad(2, "0").
2Keep a separate List of month keys in first-seen order, then call .sort() on it before building the final list.
3Guard: if startDate == null || endDate == null || startDate > endDate, return an empty list immediately.
MediumApex Class
39. Calculate SLA Due Dates While Excluding Weekends
Problem #460 · Salesforce Apex Coding Challenge
Business Scenario
Support's SLA policy grants agents a number of business days (Monday
through Friday) to resolve a Case, starting from when it was opened. Weekends must never
count toward the SLA clock, or agents effectively lose time they were promised.
Requirements
Build calculateDueDate(Datetime startDateTime, Integer businessDays)
that advances one calendar day at a time, only counting weekdays, until
businessDays weekdays have been added.
Saturday and Sunday must be skipped entirely — they never count and the due date
itself should never land on one (advancing day-by-day naturally guarantees this).
Invalid input (null start, null/zero/negative businessDays) should return the
original startDateTime unchanged rather than throwing.
Best Practices
Datetime.format('u') returns the ISO day-of-week ('1' Monday through '7'
Sunday) without any manual modulo-arithmetic date math, which is easy to get off-by-one.
Isolating the weekend check into its own isWeekend helper keeps the
day-counting loop readable and makes the rule easy to unit test in isolation.
Approach
1Datetime.format('u') returns a String: '1' for Monday up through '7' for Sunday — compare against '6' and '7' for the weekend check.
2Advance current = current.addDays(1) inside the loop, then only increment your counter when the new day is not a weekend.
3Guard clause first: null startDateTime, null businessDays, or businessDays <= 0 should all just return startDateTime as-is.
HardApex Class
40. Create a Reusable Tax Calculation Engine Supporting Multiple Countries
Problem #461 · Salesforce Apex Coding Challenge
Business Scenario
Finance needs a single Apex service that computes tax for an order's taxable amount,
but the calculation rules differ per country: some use a flat percentage, others use
progressive tiers based on the amount. New countries are added periodically, and the rules
should not require rewriting a giant if/else chain each time.
Requirements
Define a TaxStrategy interface with one method,
calculateTax(Decimal taxableAmount).
Implement at least FlatRateTaxStrategy (a single percentage) and
TieredTaxStrategy (rate depends on which threshold the amount clears).
Expose a single entry point calculateTax(String countryCode, Decimal taxableAmount)
that looks up the right strategy for the country and delegates to it, falling back to a
default strategy for unrecognized countries.
Best Practices
The Strategy pattern (an interface + a Map of country to implementation) lets new tax
rules be added by registering a new strategy instance, without touching existing logic —
this is the reusability the scenario calls for.
Every strategy is null-safe on its input and rounds the result with
setScale(2) for currency-correct output.
Approach
1The interface is the key to extensibility — calculateTax(String countryCode, Decimal amount) should never need an if/else per country.
2For TieredTaxStrategy, loop thresholds in ascending order and keep overwriting "rate" whenever amount >= thresholds[i] — the last one that matches wins.
3Build STRATEGY_BY_COUNTRY as a static final Map so adding a country is a one-line change.
HardApex Class
41. Apply Dynamic Pricing Rules Based on Customer Segments
Problem #462 · Salesforce Apex Coding Challenge
Business Scenario
Revenue operations wants product pricing to automatically flex based on the purchasing
Account's customer segment (Enterprise, Mid-Market, or
SMB, tracked on a custom Account field Customer_Segment__c) and a
seasonal peak surcharge that applies during November and December.
Requirements
Build calculatePrice(Decimal listPrice, String customerSegment, Date effectiveDate).
Apply a segment discount first (Enterprise 20%, Mid-Market 10%, SMB 5%, unrecognized
segments get no discount), then apply an 8% peak-season surcharge if
effectiveDate falls in November or December.
Null listPrice must be treated as 0 rather than throwing a null pointer
exception.
Best Practices
Segment discount lookup is isolated into its own private helper method, keeping
calculatePrice focused on the overall pricing sequence (discount, then
surcharge) rather than a long nested conditional.
All percentages are named constants so Finance-driven rate changes are a one-line
edit, not a hunt through arithmetic expressions.
Approach
1Compute the discount first, then apply the surcharge on the already-discounted price — order matters here.
2effectiveDate.month() returns 1-12; check it against a Set{11, 12} for peak months.
3getDiscountForSegment should return 0 (not throw) for any segment string it does not recognize.
Halfway there — solve them live
Every problem above runs in a real in-browser Apex editor with instant pass/fail feedback and best-practice linting. No Salesforce org needed.
42. Calculate Employee Bonuses Using Configurable Business Rules
Problem #463 · Salesforce Apex Coding Challenge
Business Scenario
HR wants a configurable bonus calculation for the custom object
Employee__c (fields Salary__c, Performance_Rating__c
1-5, and Years_Of_Service__c). Bonuses combine a performance component and a
capped loyalty component, but only employees who meet a minimum performance bar qualify at
all.
Requirements
Employees rated below 3 receive no bonus at all.
Performance bonus: 2% of salary per rating point (e.g. rating 4 → 8%).
Loyalty bonus: 0.5% of salary per year of service, capped at 5% regardless of
tenure.
Total bonus = (performance % + loyalty %) × salary, and provide a bulk wrapper
returning a Map<Id, Decimal> for a list of employees.
Best Practices
Every rate and threshold (rating cutoff, per-point/per-year percentages, loyalty cap)
is a named constant, since HR routinely renegotiates these business rules.
The single-employee method is pure and side-effect free, so it can be reused directly
by both a bulk wrapper and, e.g., a preview UI — no SOQL or DML lives inside it.
Approach
1Return 0 immediately when rating < MIN_QUALIFYING_RATING — no further calculation needed.
2Cap the loyalty bonus with a simple if check after computing years * LOYALTY_BONUS_PCT_PER_YEAR.
3The bulk wrapper should not duplicate any math — just call calculateBonus once per employee.
HardApex Class
43. Build a Reusable Address Validation Service Class
Problem #464 · Salesforce Apex Coding Challenge
Business Scenario
Multiple objects (Account billing address, Contact mailing address, a future Order
shipping address) all need the same address-quality checks before being saved. Build one
reusable service so the validation rules live in exactly one place.
Requirements
validateAddress(street, city, state, postalCode, country) returns a
ValidationResult inner class holding isValid and a list of
human-readable errors.
Street and city are always required. For US addresses (country is
USA/US/blank), the state must be a valid 2-letter code and the
postal code must match US ZIP format (12345 or 12345-6789).
Non-US addresses only require a non-blank postal code.
Provide a bulk wrapper validateAccounts(List<Account>) returning a
Map<Id, ValidationResult> for the standard Account billing address
fields.
Best Practices
Accepting individual field values (rather than a specific SObject type) is what makes
validateAddress reusable across Account, Contact, and any future object —
the bulk wrapper adapts a specific SObject's fields to that generic signature.
Collecting every failure into a list (instead of stopping at the first error) gives
the caller the full picture in one pass, which is friendlier for a UI or bulk import.
Approach
1ValidationResult.addError should both flip isValid to false and append to the errors list — keep it in one method so callers never forget one half.
2Pattern.compile('^\\d{5}(-\\d{4})?$') matches both 5-digit and ZIP+4 formats — remember Apex string literals need \\\\ for a single regex backslash.
3Treat a blank/null country as US — most Salesforce orgs default new records to the org-level default country.
ExpertApex Class
44. Calculate Subscription Renewal Amounts With Discounts
Problem #465 · Salesforce Apex Coding Challenge
Business Scenario
Customer Success wants renewal quotes computed automatically for the custom object
Subscription__c (Base_Amount__c, Consecutive_Renewals__c,
Auto_Renew__c checkbox, Contract_Term_Months__c). Multiple
discounts can stack: loyalty (more consecutive renewals = bigger discount, capped),
auto-renew enrollment, and committing to an annual-or-longer term.
Requirements
Loyalty discount: 2% per consecutive renewal, capped at 10% total.
Auto-renew discount: flat additional 5% if Auto_Renew__c is true.
Annual term discount: flat additional 8% if Contract_Term_Months__c >= 12.
All applicable discounts stack additively against the base amount, then apply once.
Provide both a single-subscription method and a bulk
Map<Id, Decimal> wrapper.
Best Practices
Math.min() enforces the loyalty discount cap cleanly instead of a
manual if-check, and keeps the cap logic in one expression.
Discounts are summed into a single totalDiscountPct and applied once at
the end — avoids compounding rounding errors from applying each discount sequentially to
an already-discounted amount.
All rates and caps are named constants for easy business-rule tuning.
Approach
1Math.min(renewals * LOYALTY_DISCOUNT_PCT_PER_RENEWAL, MAX_LOYALTY_DISCOUNT_PCT) caps the loyalty discount in one line.
2Sum every applicable discount percentage into a single total before doing the final multiplication — do not apply discounts one after another to a shrinking amount.
3autoRenew == true treats a null Boolean safely as false without throwing.
Sales wants a "customers who bought this also bought that" recommendation feature for
an Account's product page: given an Account, suggest up to 5 Products that similar
customers purchased but this Account has not, ranked by how often those similar customers
bought each candidate product.
Requirements
Build getRecommendations(Id accountId) returning up to 5
Product2 records.
Step 1: find products this Account has already purchased (Closed Won
OpportunityLineItems).
Step 2: find other Accounts ("similar accounts") that purchased at least one of the
same products.
Step 3: among those similar accounts' other purchases (excluding products this
Account already owns), rank candidate products by purchase frequency and return the
top 5.
Best Practices
Each stage of the algorithm is a single aggregate SOQL query — no per-record queries
inside a loop, even though the logic spans three distinct steps.
WITH SECURITY_ENFORCED and a LIMIT on every query keeps
each stage safe and bounded regardless of how large the customer base grows.
Early-return guard clauses after each stage (no purchases, no similar accounts, no
co-occurring products) avoid unnecessary downstream queries when there is nothing left
to compute.
Approach
1GROUP BY PricebookEntry.Product2Id on OpportunityLineItem gives distinct purchased product Ids for an Account.
2Chain the three aggregate queries: purchased products → similar accounts → co-occurring products, returning early whenever a Set comes back empty.
3ORDER BY COUNT(Id) DESC with LIMIT :MAX_RECOMMENDATIONS on the final aggregate ranks candidates by frequency in one query.
ExpertApex Class
46. Generate Financial Reports Grouped by Fiscal Quarter
Problem #467 · Salesforce Apex Coding Challenge
Business Scenario
Finance wants a Closed Won revenue report broken down by fiscal quarter, where the
fiscal year does not necessarily start in January — the org's fiscal year start month is a
configurable input (e.g. 2 means the fiscal year runs February through
January).
Requirements
Build getQuarterlyReport(Integer year, Integer fiscalYearStartMonth)
returning exactly four QuarterSummary entries (Q1-Q4) — total revenue and
deal count of Closed Won Opportunities closing in that fiscal quarter.
Compute the fiscal year's date boundaries from fiscalYearStartMonth,
then bucket each Opportunity's CloseDate into one of the four 3-month
quarters relative to that start.
Default fiscalYearStartMonth to January (1) when not supplied.
Best Practices
A single SOQL query pulls every Closed Won Opportunity in the fiscal year; quarter
bucketing is pure in-memory arithmetic, avoiding four separate per-quarter queries.
WITH SECURITY_ENFORCED and a LIMIT keep this
reporting-style query safe and bounded.
Pre-seeding summaryByQuarter with all four quarters before the loop
guarantees the report always returns Q1-Q4 even for quarters with zero deals.
Approach
1fiscalStart.addMonths(12).addDays(-1) gives the last day of the fiscal year starting at fiscalStart.
2quarterIndex = monthsSinceFiscalStart / 3 (integer division) buckets any month offset 0-11 into quarter 0-3.
3Pre-populate summaryByQuarter with all four QuarterSummary objects before the query loop so quarters with zero deals still appear in the result.
ExpertApex Class
47. Build a Generic Notification Service for Multiple Objects
Problem #468 · Salesforce Apex Coding Challenge
Business Scenario
Multiple triggers across the org (Case escalations, Opportunity stage changes, Contract
renewals) all need to notify a user, but some notifications should be emails and others
should be follow-up Tasks. Build one generic notification service so trigger code never
has to know the delivery mechanism.
Requirements
Define a NotificationChannel interface with
send(String recipientId, String subject, String body).
Implement EmailChannel (using Messaging.SingleEmailMessage
targeted at a User/Contact Id) and TaskChannel (creating a Task
owned by the recipient).
Expose notify(NotificationChannel channel, String recipientId, String subject, String body)
as the generic entry point, plus a bulk notifyMultiple(..., List<String> recipientIds, ...)
for sending the same message to many recipients.
Best Practices
The NotificationChannel interface decouples "who is calling" from "how
the message is delivered" — a new channel (e.g. Chatter post, SMS) can be added without
touching any existing trigger.
Each channel implementation wraps its own DML/callout in
try/catch (EmailException for email,
DmlException for the Task insert) so a delivery failure never bubbles up as
an unhandled exception to the caller.
Approach
1setTargetObjectId(recipientId) plus setSaveAsActivity(false) is the standard pattern for emailing a User/Contact by Id.
2notify() and notifyMultiple() should never know whether channel is an EmailChannel or TaskChannel — that is the whole point of the interface.
3Each channel handles its own failure mode: EmailException for Messaging.sendEmail, DmlException for the Task insert.
EasyApex Class
48. Calculate Working Days Between Two Dates
Problem #554 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class WorkingDaysCalculator with a static method
countWorkingDays(Date startDate, Date endDate) that returns the number of
weekdays (Monday through Friday) in the range from
startDate to endDate, inclusive of both ends.
Rules
Weekends (Saturday, Sunday) are never counted.
If startDate or endDate is null, or
startDate is after endDate, return 0.
Hint on Day-of-Week
Apex's Date class has no built-in "day of week" method. The standard technique:
pick any date whose weekday you know for certain, then use
Date.daysBetween() together with Math.mod() to find the offset
from that reference day.
Method Signature
public static Integer countWorkingDays(Date startDate, Date endDate)
Approach
1January 1, 2024 was a Monday — use it as a fixed reference point.
2Math.mod(refMonday.daysBetween(current), 7) gives 0 for Monday, 5 for Saturday, 6 for Sunday.
3Loop from startDate to endDate using current.addDays(1), counting only non-weekend offsets.
EasyApex Class
49. Convert Seconds Into Human-Readable Duration
Problem #555 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class DurationFormatter with a static method
formatDuration(Integer totalSeconds) that converts a duration in seconds
into a human-readable string.
Format Rules
Always show seconds, e.g. "45s".
Show minutes if there is at least 1 full minute (or at least 1 hour), e.g. "2m 5s".
Show hours only if there is at least 1 full hour, e.g. "1h 1m 1s".
null or a negative number returns "0s".
Examples
3661 → "1h 1m 1s"
125 → "2m 5s"
45 → "45s"
0 → "0s"
Method Signature
public static String formatDuration(Integer totalSeconds)
Approach
1Integer division truncates in Apex: hours = totalSeconds / 3600.
2Subtract whole hours/minutes before computing the next smaller unit.
3Only prepend the "h"/"m" segments when they should actually appear.
MediumApex Class
50. Find the Most Frequent Value in a List
Problem #556 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class FrequencyAnalyzer with a static method
mostFrequent(List<Integer> nums) that returns the value that appears
most often in the list.
Rules
If there is a tie for the highest frequency, return whichever tied value
appears first in the input list.
public static Integer mostFrequent(List<Integer> nums)
Approach
1Build a Map of value -> count first.
2Then scan the ORIGINAL list again, tracking the best count seen so far with a strict ">" comparison — this naturally keeps the first value to reach the max on a tie.
EasyApex Class
51. Remove Consecutive Duplicate Values From a List
Problem #557 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class DedupUtil with a static method
removeConsecutiveDuplicates(List<String> items) that collapses runs of
consecutive equal values down to a single occurrence (similar to the Unix uniq
command). Non-consecutive repeats are left alone.
Examples
['a','a','b','b','b','a'] → ['a','b','a']
['x','y','x'] → ['x','y','x'] (no consecutive repeats)
null returns an empty list.
Method Signature
public static List<String> removeConsecutiveDuplicates(List<String> items)
Approach
1Track the previous value seen as you loop.
2Always add the very first element regardless of any "previous" value.
EasyApex Class
52. Find Common Elements Between Two Sets
Problem #558 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class SetUtil with a static method
findCommon(Set<String> setA, Set<String> setB) that returns a new
Set<String> containing only the values present in both
sets (their intersection).
A null set is treated as empty.
Method Signature
public static Set<String> findCommon(Set<String> setA, Set<String> setB)
Approach
1Loop through setA and check setB.contains(value).
2Guard against either set being null.
EasyApex Class
53. Generate a Numeric Sequence With Configurable Step
Problem #559 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class SequenceGenerator with a static method
generateSequence(Integer start, Integer end, Integer step) that returns a
List<Integer> starting at start, increasing by
step each time, up to and including end.
Rules
If step is null or not positive, return an empty list.
If start is after end, return an empty list.
Examples
(1, 10, 2) → [1, 3, 5, 7, 9]
(5, 5, 1) → [5]
Method Signature
public static List<Integer> generateSequence(Integer start, Integer end, Integer step)
Approach
1Use a while loop: while (current <= end) { ... current += step; }
2Validate step > 0 before looping, or it never terminates.
EasyApex Class
54. Validate Whether a String Contains Only Alphanumeric Characters
Problem #560 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class StringValidator with a static method
isAlphanumeric(String input) that returns true only if every
character in input is a letter (A-Z, a-z) or a digit (0-9), and the string is
non-empty.
null or an empty string returns false.
Method Signature
public static Boolean isAlphanumeric(String input)
Approach
1Pattern.compile('[A-Za-z0-9]+').matcher(input).matches() requires the WHOLE string to match.
2Check for null/empty before running the pattern.
EasyApex Class
55. Convert a Delimited String Into a Set of Values
Problem #561 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class DelimitedStringParser with a static method
toValueSet(String csv, String delimiter) that splits csv on
delimiter, trims whitespace from each piece, discards any blank pieces, and
returns the unique remaining values as a Set<String>.
null or blank csv returns an empty set.
Example
toValueSet(" a, b ,c,, a", ",") → {'a', 'b', 'c'}
Method Signature
public static Set<String> toValueSet(String csv, String delimiter)
Approach
1String.isBlank(csv) catches both null and whitespace-only input.
2csv.split(delimiter) returns a List — trim() each entry before adding it.
MediumApex Class
56. Calculate Compound Interest for a Customer Investment
Problem #562 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class InvestmentCalculator with a static method
calculateCompoundInterest(Decimal principal, Decimal annualRatePercent, Integer years)
that returns the final investment value after compounding annually for
the given number of years, rounded to 2 decimal places.
Formula: finalAmount = principal * (1 + rate/100)^years
Invalid input (any null argument, or negative years) returns 0.
Method Signature
public static Decimal calculateCompoundInterest(Decimal principal, Decimal annualRatePercent, Integer years)
Approach
1Apex has no exponent operator — use Math.pow(base, exponent).
2Decimal.valueOf(x).setScale(2) rounds to 2 decimal places.
MediumApex Class
57. Determine Shipping ETA From Order Date and Delivery Zone
Problem #563 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ShippingEstimator with two static methods:
getZoneDays(String zone) — returns the number of delivery days for a
zone: 'LOCAL' → 2, 'REGIONAL' → 5,
'INTERNATIONAL' → 10. Any other value (including null) returns
null.
calculateETA(Date orderDate, String zone) — returns
orderDate plus the zone's delivery days. If the zone is unrecognized or
either argument is null, returns null.
Method Signatures
public static Integer getZoneDays(String zone)
public static Date calculateETA(Date orderDate, String zone)
Approach
1Keep the zone → day-count mapping in its own method so it is easy to test directly.
2calculateETA should return null immediately if getZoneDays(zone) comes back null.
HardApex Class
58. Apex REST: Expose an Endpoint for Customer Preference Retrieval
Problem #630 · Salesforce Apex Coding Challenge
Problem Statement
Write a REST resource CustomerPreferenceResource exposed at
/CustomerPreferences/* that accepts a GET request with the Account Id as
the URL suffix, and returns that account's Communication_Preference__c and
Marketing_Opt_In__c as JSON. A missing or unknown Id returns a 404.
Approach
1@RestResource(urlMapping='/CustomerPreferences/*') on the class, @HttpGet on the method.
2The Id suffix comes from RestContext.request.requestURI — split on '/' and take the last segment.
3Set RestContext.response.statusCode = 404 directly when the account cannot be found.
Write a REST resource ProductAvailabilityResource at
/ProductAvailability accepting a POST with a JSON body
{"productIds": [...]}, and responding with each product's
Id and QuantityOnHand__c as a JSON array — bulk-safe for any
number of Ids in one request.
Approach
1Deserialize RestContext.request.requestBody (a Blob) via .toString(), then JSON.deserializeUntyped.
2A single SOQL query with WHERE Id IN :productIds serves the whole batch.
HardApex Class
60. Apex REST: Implement Pagination for Custom API Responses
Problem #632 · Salesforce Apex Coding Challenge
Problem Statement
Write a REST resource PagedAccountResource at
/Accounts supporting GET with page and pageSize
query parameters, returning that page of Accounts along with
totalRecords and totalPages in the response JSON.
Approach
1Read query params via RestContext.request.params.get('page') — they always come in as Strings, so parse them with Integer.valueOf.
2offset = (page - 1) * pageSize is the standard 1-indexed pagination formula.
MediumApex Class
61. Apex REST: Return Standardized Error Responses for Invalid Requests
Problem #633 · Salesforce Apex Coding Challenge
Problem Statement
Write a REST resource LeadIntakeResource at
/LeadIntake accepting a POST with {"email": "...", "company":
"..."}. If either field is missing or blank, respond with status
400 and a standardized body {"error": "...", "field":
"..."} naming the first missing field — never let an unhandled exception
escape.
Approach
1Check email first, then company, so the response names whichever ONE field is the actual problem.
2Wrap the whole thing in try/catch(Exception e) as a last resort, also returning a standardized error body on status 500.
ExpertApex Class
62. Apex REST: Implement Idempotent External Order Submission
Problem #634 · Salesforce Apex Coding Challenge
Problem Statement
Write a REST resource OrderSubmissionResource at
/OrderSubmission accepting a POST with
{"idempotencyKey": "...", "accountId": "...", "totalAmount": ...}. If an
Order already exists with that Idempotency_Key__c, return the
existing order's Id with status 200 instead of creating a duplicate;
otherwise insert a new one and return 201. A failed insert (e.g. a
validation rule rejecting the order) must return status 500 with an error
body, never an unhandled exception.
Approach
1Query for an existing Order by Idempotency_Key__c FIRST, before doing anything else.
2The status code is the signal to the caller for whether this was a fresh creation (201) or a replay (200) — the response body's Order Id is the same either way.
3Wrap the insert in try/catch(DmlException) — an unhandled exception from a REST resource surfaces as an opaque platform error instead of a controlled response the caller can act on.
HardApex Class
63. Integration: Parse a Nested JSON Product Catalog Into Apex Objects
Problem #635 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ProductCatalogParser with a static method
parseCatalog(String jsonString) that parses a nested JSON product catalog
(shape: {"products": [{"name":..., "sku":..., "pricing": {"listPrice":...}}]})
into a flat List<Map<String, Object>>, each entry holding
name, sku, and listPrice pulled up from the
nested pricing object.
A product missing the pricing object entirely gets a null
listPrice, not an error.
Method Signature
public static List<Map<String, Object>> parseCatalog(String jsonString)
Approach
1JSON.deserializeUntyped(jsonString) gives you nested Map/List
64. Integration: Transform External Customer JSON Into Salesforce Records
Problem #636 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class CustomerJsonTransformer with a static method
transformToContacts(String jsonString) that converts a JSON array of
external customer records (snake_case keys: first_name,
last_name, email_address) into a
List<Contact> ready for insert.
Method Signature
public static List<Contact> transformToContacts(String jsonString)
Approach
1JSON.deserializeUntyped on an array-shaped JSON string gives you a List
2Cast each entry to Map before pulling out the snake_case keys.
HardApex Class
65. Integration: Handle an External API Response Containing Multiple Error Types
Problem #637 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ApiErrorHandler with a static method
classifyError(Integer statusCode, String responseBody) that classifies an
external API's response into one of:
401 or 403 → 'AUTH_ERROR'
429 → 'RATE_LIMITED'
500 and above → 'SERVER_ERROR'
Any other 400+ whose body contains a
validationErrors key → 'VALIDATION_ERROR'
Any other 400+ → 'CLIENT_ERROR'
Anything below 400 → 'SUCCESS'
Method Signature
public static String classifyError(Integer statusCode, String responseBody)
Approach
1Check the most specific codes (401/403, 429) before the broad >= 500 and >= 400 ranges.
2Only parse responseBody as JSON once you already know the status is in the 400+ range — a 200 response body might not even be JSON-shaped the same way.
MediumApex Class
66. Integration: Implement Named Credential Based API Authentication
Problem #638 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class InventoryApiClient with a static method
fetchInventoryLevel(String sku) that calls
callout:InventoryApi/stock?sku= + the SKU using a named credential (so no
credentials are ever hardcoded in Apex), and returns the parsed
quantityAvailable from the JSON response.
Approach
1A "callout:" endpoint prefix is what tells Salesforce to inject the Named Credential's stored authentication automatically — never build an Authorization header by hand alongside one.
2Only parse the response body when the callout actually succeeded (status 200).
HardApex Class
67. Integration: Build a Reusable JSON Request Builder for External APIs
Problem #639 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class RequestPayloadBuilder with a static method
buildOrderPayload(Id accountId, Decimal totalAmount, List<String>
productSkus) that assembles and serializes a JSON payload shaped
{"accountId":..., "totalAmount":..., "items":[...]} for an external order
API — decoupling payload construction from the actual HTTP call so it can be tested and
reused on its own.
Method Signature
public static String buildOrderPayload(Id accountId, Decimal totalAmount, List<String> productSkus)
Approach
1Build a Map first, then JSON.serialize(...) it once at the end — do not hand-build the JSON text with string concatenation.
2The List of SKUs can be dropped straight into the map under the "items" key; JSON.serialize handles nested collections on its own.
ExpertApex Class
68. Advanced Apex: Build a Metadata-Driven Field Transformation Engine
Problem #640 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class FieldTransformationEngine with a static method
applyTransformations(List<SObject> records) that reads
Field_Transformation_Rule__mdt custom metadata records
(Source_Field__c, Target_Field__c,
Transformation_Type__c of 'UPPERCASE',
'LOWERCASE', or 'TRIM') and applies each rule to every record
generically, using dynamic get()/put() field access rather
than any hardcoded field name.
Approach
1Query the rule metadata once, outside the record loop — never per-record.
2sObject.get(fieldApiName) and sObject.put(fieldApiName, value) work with a field name known only at runtime.
ExpertApex Class
69. Advanced Apex: Build a Generic Record Comparison Utility Using Describe Information
Problem #641 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class RecordComparisonUtil with a static method
compareRecords(SObject before, SObject after) that returns a
Map<String, Object> of every field that differs between the two
records, using getSObjectType().getDescribe().fields.getMap() to discover
the full field list generically — no hardcoded field names for any specific object.
Approach
1before.getSObjectType().getDescribe().fields.getMap() returns every field name available on that SObject type, regardless of which object it is.
2Store each difference as its own before/after pair so the caller can see both values, not just that something changed.
MasterApex Class
70. Advanced Apex: Create a Generic CSV-to-SObject Import Framework
Problem #642 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class CsvImportFramework with a static method
parseCsv(String csvContent, String sObjectApiName) that parses CSV text
(first line = headers) into a List<SObject> of the given object type,
built dynamically via Schema.getGlobalDescribe() — the same method must
work for any object whose API name is passed in, not just one hardcoded type.
Approach
1Schema.getGlobalDescribe().get(sObjectApiName).newSObject() creates a blank instance of whatever type name was passed in.
2Trim both header names and values — CSV text commonly has stray whitespace around commas.
MasterApex Class
71. Advanced Apex: Build a Configurable Rule Evaluation Engine Using Custom Metadata
Problem #643 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class RuleEvaluationEngine with a static method
evaluateDiscount(Decimal orderAmount) that looks up
Discount_Rule__mdt records (Min_Amount__c,
Discount_Percent__c) and returns the discount percentage for the
highest minimum-amount tier the order qualifies for — so admins can add
new pricing tiers by deploying metadata, with no Apex code change.
Approach
1Order the query by Min_Amount__c DESC so the first tier the order still qualifies for is the highest (best-fitting) one.
2Return 0 when no tier's Min_Amount__c is met at all.
MasterApex Class
72. Advanced Apex: Create a Generic Parent-Child Data Export Framework
Problem #644 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ParentChildExportFramework with a static method
exportWithChildren(List<SObject> parents, String childRelationshipName)
that turns each parent (with its queried child relationship already populated) into a
Map<String, Object> of its own fields plus a
"children" key holding the same generic shape for each child record —
working for any object/relationship pair, not a specific one.
Approach
1sObject.getPopulatedFieldsAsMap() generically returns every field that actually has a value, with no hardcoded field list.
2sObject.getSObjects(relationshipName) retrieves an already-queried child relationship by its name — it returns null (not an empty list) when nothing was queried for that relationship.
MasterApex Class
73. Advanced Apex: Build a Transaction-Safe Multi-Object Import Processor
Problem #645 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class MultiObjectImportProcessor with a static method
importAll(List<Account> accounts, List<Contact> contacts) that
inserts the accounts, links any contact missing an AccountId to the first
inserted account, then inserts the contacts — rolling the entire
operation back to a savepoint if either insert fails, so accounts are never left
orphaned without their contacts.
Approach
1Database.setSavepoint() before the first DML, Database.rollback(sp) in the catch block.
2Return a Boolean so the caller can tell success from a rolled-back failure without needing to inspect an exception.
ExpertApex Class
74. Advanced Apex: Design a Pluggable Notification Framework Using Interfaces
Problem #646 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface NotificationChannel with a single method
send(String recipient, String message), then implement it twice —
EmailNotificationChannel (using Messaging.SingleEmailMessage)
and SmsNotificationChannel (a stand-in that just logs, since this org has no
real SMS gateway) — and a NotificationDispatcher class whose
dispatch(NotificationChannel channel, String recipient, String message)
method works with either implementation without ever checking which one
it received.
Approach
1The whole point of coding to the interface type (NotificationChannel channel) as the parameter is that dispatch() never needs an if/instanceof check for which implementation it got.
2Messaging.SingleEmailMessage needs setToAddresses(), setSubject(), and setPlainTextBody() before Messaging.sendEmail() can send it.
ExpertApex Class
75. Advanced Apex: Build a Strategy Pattern for Multiple Pricing Algorithms
Problem #647 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface PricingStrategy with
Decimal calculatePrice(Decimal basePrice, Integer quantity), then implement
BulkDiscountPricingStrategy (10% off at 50+ units, 20% off at 100+) and
FlatRatePricingStrategy (no discount at all). PriceCalculator
is constructed with whichever strategy to use and delegates every
calculate() call to it.
Approach
1Store the injected PricingStrategy in a private field set from the constructor.
2Check the 100+ threshold before the 50+ threshold, or the bigger discount would never be reached.
MasterApex Class
76. Advanced Apex: Implement a Generic Cache Abstraction With Multiple Storage Strategies
Problem #648 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface CacheStore with get(String key) and
put(String key, Object value), then implement
PlatformCacheStore (backed by Cache.Org) and
StaticMapCacheStore (a plain in-memory Map, useful in tests). A
CacheManager constructed with either store exposes
getOrCompute(String key, Object computedValue): return the cached value if
present, otherwise store and return computedValue.
Approach
1StaticMapCacheStore's backing Map must be static so it survives across separate CacheManager instances within the same transaction.
2getOrCompute() should only call store.put() on a cache miss, never on a hit.
MasterApex Class
77. Advanced Apex: Build a Dependency-Injection-Based Service Layer
Problem #649 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface AccountService with
processAccount(Account acc), a real implementation
DefaultAccountService, and a ServiceFactory that returns the
right implementation via getAccountService() — supporting
setMock(String serviceName, Object mockImpl) so test code can substitute a
fake implementation without touching the factory's real logic.
Approach
1A static Map on the factory is the simplest way to hold registered mock overrides.
2getAccountService() should check the mock map FIRST, only falling back to `new DefaultAccountService()` when nothing was registered.
MasterApex Class
78. Advanced Apex: Design a Transaction Rollback Framework for Multi-Step Processing
Problem #650 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface ProcessStep with a single method
run(), and a TransactionalStepRunner whose
runSteps(List<ProcessStep> steps) executes each step in order inside
one savepoint-protected transaction — if any step throws, every
previously-completed step in that same call is rolled back too, and the method returns
false instead of letting the exception escape.
Approach
1One Savepoint covers the WHOLE list of steps, not one per step — that is what makes a mid-list failure undo the earlier steps too.
2The loop itself needs no special per-step try/catch; the single outer try/catch around the whole loop is sufficient and correct.
MasterApex Class
79. Advanced Apex: Build a Configurable Retry Framework for Recoverable Operations
Problem #651 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface RetryableOperation with
execute(), and a RetryFramework whose
executeWithRetry(RetryableOperation operation, Integer maxAttempts)
retries a failing operation up to maxAttempts times, returning
true as soon as one attempt succeeds and false only after
every attempt has been exhausted.
Approach
1A while loop bounded by maxAttempts, with a try/catch inside it, is the standard shape for this.
2Log each individual failed attempt as it happens, then log a final message once maxAttempts is actually exhausted.
MasterApex Class
80. Advanced Apex: Create a Generic Audit Framework for Configured Objects and Fields
Problem #652 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class AuditFramework with a static method
logChanges(List<SObject> newRecords, Map<Id, SObject> oldRecordsMap,
String objectApiName) that reads which fields to watch from
Audit_Config__mdt (Object_API_Name__c,
Field_API_Name__c) and inserts one Audit_Log__c per changed,
configured field — generic enough to be called from any object's trigger just by passing
a different objectApiName.
Approach
1Filter the metadata query to WHERE Object_API_Name__c = :objectApiName so only the relevant fields for THIS object are checked.
2Use record.get(fieldName) dynamically, not a hardcoded field, so the same loop body works for any configured field.
3Wrap the audit log insert in its own try/catch — this runs as a side effect of the caller's real transaction, so a failure writing the audit trail must never roll back or block the actual business change.
MasterApex Class
81. Advanced Apex: Build a Pluggable Data Masking Service for Non-Production Environments
Problem #653 · Salesforce Apex Coding Challenge
Problem Statement
Define an interface MaskingStrategy with
String mask(String value), then implement
EmailMaskingStrategy (keeps the first character and the domain, e.g.
"j***@acme.com") and PhoneMaskingStrategy (keeps only the
last 4 digits, e.g. "***-***-1234"). DataMaskingService
exposes applyMask(MaskingStrategy strategy, String value) so sandbox
refresh scripts can mask any field just by choosing a different strategy.
Approach
1String.indexOf('@') finds where the domain starts for the email strategy.
2String.right(n) returns the last n characters — exactly what the phone strategy needs to keep.
Practice All 81 Problems Free
Write and run real Apex code right in your browser — instant pass/fail feedback, best-practice linting, and governor limit monitoring. No Salesforce org needed.
ApexArena is a free, browser-based Salesforce Apex coding practice platform covering every major topic tested on the Salesforce Platform Developer I (PD1) and Platform Developer II (PD2) certification exams. All problems run directly in your browser with instant pass/fail feedback, best-practice linting (SOQL in loops, DML in loops, empty catch blocks), and governor limit monitoring — no Salesforce Developer Edition org required.