intermediate
Career

PD1 Practice Questions: What the Exam Actually Tests

Seven PD1 practice questions with worked answers, covering bulkification, trigger context, DML partial success, and the Apex testing rules the exam keeps asking about.

8 min read
Warren Walters
pd1
certification
apex
triggers
testing

TL;DR

The Platform Developer I exam is 60 questions in 105 minutes, and you pass at 68%. Most of it is Apex behavior, not Apex syntax. This post works through seven questions in the style the exam uses, and explains why the wrong answers look right. Each one points at a challenge where you write the code yourself.

What The Exam Weighs

Salesforce splits PD1 into four sections. The weights matter, because they tell you where to spend your study time.

SectionWeightRough question count
Developer Fundamentals23%14
Process Automation and Logic30%18
User Interface25%15
Testing, Debugging, and Deployment22%13

Process Automation and Logic is the biggest slice. That section is Apex, triggers, SOQL, and flow. Testing is another 22%. Together they are more than half the exam, and they are the half you can practice by writing code.

Weights get revised, so check the official exam guide before you book. The shape has been stable for a while, though.

The questions below follow the exam's habit: a short scenario, four answers, and at least two that look correct. Read the scenario, pick an answer, then read why.

Question 1: The Query Inside The Loop

A developer writes a trigger that loops over 200 accounts and queries contacts for each one. The trigger fails in production. What is the cause?

  • A. The trigger is missing a before insert context
  • B. The code hits the SOQL query limit
  • C. Accounts cannot have more than 200 contacts
  • D. The trigger needs @future

Answer: B. Apex allows 100 SOQL queries per transaction. A query inside a loop over 200 records runs 200 times, so it fails on record 101.

The fix is to query once, outside the loop, and use a map to line the data up.

Map<Id, Account> accountsById = new Map<Id, Account>(
    [SELECT Id, Name FROM Account LIMIT 200]
);
 
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [
    SELECT Id, LastName, AccountId
    FROM Contact
    WHERE AccountId IN :accountsById.keySet()
]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}
 
System.debug(contactsByAccount.size());

Two queries, no matter how many accounts. This is bulkification, and the exam tests it more than any other single idea. Option D is the trap: @future moves the work to another transaction, which gets its own limits, but the loop is still a loop. You have hidden the bug, not fixed it.

Count the queries yourself on the governor limits challenge.

Question 2: Which Trigger Context

A developer needs to set a field on an account before it is saved, without a second DML statement. Which trigger context works?

  • A. after insert
  • B. before insert
  • C. after update
  • D. before delete

Answer: B. In a before trigger, the records in Trigger.new have not been written yet. You change a field and the platform saves your change with the rest of the record. No DML needed.

In an after trigger the record is already saved, and the fields in Trigger.new are read-only. Changing a field there throws at runtime.

The rule is short enough to memorize. Use before to change the record that fired the trigger. Use after to work with other records, or when you need the new record's Id, which does not exist until the insert completes.

trigger AccountTrigger on Account (before insert) {
    for (Account a : Trigger.new) {
        // Safe: the record is not saved yet.
        a.Description = 'Created by trigger';
    }
}

Question 3: Running The Same Trigger Twice

An update inside an after update trigger causes the same trigger to fire again. What is the standard way to stop it?

  • A. Move the logic to a before trigger
  • B. Use a static boolean in a helper class
  • C. Add @future to the method
  • D. Wrap the DML in a try-catch

Answer: B. A static variable lives for the length of one transaction. You set it the first time through and check it on every entry after that.

public class TriggerGuard {
    public static Boolean hasRun = false;
 
    public static void handle(List<Account> records) {
        if (hasRun) {
            return;
        }
        hasRun = true;
        System.debug('Processing ' + records.size() + ' records');
    }
}

Option A sometimes works by accident and sometimes does not, so it is not the answer to a question about recursion. Option D catches an error that has already happened. Neither stops the second run.

Write the guard yourself on the trigger recursion challenge.

Question 4: When One Record In The Batch Is Bad

A developer inserts 200 accounts. One of them violates a validation rule. The other 199 should still save. Which call does that?

  • A. insert accounts;
  • B. Database.insert(accounts, false);
  • C. Database.insert(accounts, true);
  • D. upsert accounts;

Answer: B. The second argument is allOrNone. Pass false and the good records commit while the bad ones come back as failed results.

The plain insert keyword, and Database.insert with true, both roll the whole batch back when any record fails. That is the default, and it is the right default for most code. It is the wrong default when you are loading data and one bad row should not stop the other 199.

Database.insert with false hands you a result per record, in the same order you passed them in.

List<Account> accounts = new List<Account>{
    new Account(Name = 'Good Account'),
    new Account()
};
 
List<Database.SaveResult> results = Database.insert(accounts, false);
for (Integer i = 0; i < results.size(); i++) {
    if (!results[i].isSuccess()) {
        System.debug('Row ' + i + ' failed: ' + results[i].getErrors()[0].getMessage());
    }
}

The exam likes this one because the failure is silent. Nothing throws. If you do not read the results, you never learn which rows were dropped.

Handle the failures on the DML error handling challenge.

Question 5: What Test Methods Can See

A test method creates an account, then queries for it. The query returns nothing. What is the most likely cause?

  • A. The test needs @isTest(SeeAllData=true)
  • B. The account was never inserted
  • C. Test methods cannot query
  • D. The query needs WITH USER_MODE

Answer: B. Building an Account in memory does not save it. Until you call insert, there is no row to find.

This question is really about the option A trap. SeeAllData=true makes a test read your org's real data, which is exactly what you do not want. Tests that depend on org data pass on your sandbox and fail on someone else's. Create your own data instead.

@isTest
private class AccountServiceTest {
    @isTest
    static void findsInsertedAccount() {
        Account a = new Account(Name = 'Test Co');
        insert a;
 
        List<Account> found = [SELECT Id, Name FROM Account WHERE Name = 'Test Co'];
        System.assertEquals(1, found.size(), 'Expected the inserted account');
    }
}

Test data is rolled back when the test finishes, so nothing you insert in a test survives it.

Question 6: Async Work In A Test

A test calls a method that queues a future job. The test asserts on the result and fails, even though the code is correct. What fixes it?

  • A. Put the call between Test.startTest() and Test.stopTest()
  • B. Add a Thread.sleep call
  • C. Mark the test method @future
  • D. Query the job from AsyncApexJob

Answer: A. Async work queued inside a test does not run on its own. Test.stopTest() forces it to run and waits for it to finish. Assert after that line, not before.

@isTest
private class AsyncJobTest {
    @isTest
    static void runsQueuedWork() {
        Test.startTest();
        Account a = new Account(Name = 'Async Co');
        insert a;
        Test.stopTest();
 
        List<Account> saved = [SELECT Id FROM Account WHERE Name = 'Async Co'];
        System.assertEquals(1, saved.size(), 'Expected one account');
    }
}

The pair does one more thing worth knowing. Code between the two calls gets a fresh set of governor limits. That lets you set up 200 records first and still have the full query allowance for the code you are actually testing.

Question 7: Picking The Right Collection

A developer needs to look up a contact by its Id, thousands of times, inside a loop. Which collection should hold them?

  • A. List<Contact>
  • B. Set<Contact>
  • C. Map<Id, Contact>
  • D. A list sorted by Id

Answer: C. A map goes straight to the value for a key. A list has to walk every element until it finds a match, which turns one loop into two.

There is a constructor built for this exact case.

Map<Id, Contact> contactsById = new Map<Id, Contact>(
    [SELECT Id, LastName FROM Contact LIMIT 100]
);
 
Contact found = contactsById.get(contactsById.keySet().iterator().next());
System.debug(found.LastName);

Passing a query straight into new Map<Id, SObject>(...) keys it by record Id for free. It shows up in real trigger code constantly, which is why the exam asks.

Sets hold unique values and answer one question: is this in here? Lists keep order and allow duplicates. Maps pair a key with a value. Choosing wrong is a performance bug, not a compile error, so nothing warns you.

Brush up on the Apex collections lessons if the difference is still fuzzy.

How To Study The Rest

Seven questions is not a practice exam. It is a sample of the style, and the style is the useful part: scenario, plausible distractors, one answer that depends on knowing how the platform behaves rather than what the syntax is.

Three habits move the needle more than reading does.

Run the code. Every snippet above executes in the Developer Console. Change one thing and see what breaks. A limit you have actually hit is a limit you remember.

Read the error messages. The exam quotes them. "Too many SOQL queries: 101" and "Record is read-only" are both answers to questions you will be asked.

Practice bulkification until it is boring. It is the highest-value single topic on the exam, and it is the thing that separates code that passes review from code that does not.

Where To Practice Next

The questions above map to challenges you can solve in the browser against a real org:

Answer the question before you read the explanation. That is the part that sticks.

WW

About Warren Walters

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

Visit Profile →
Share:

Related Posts