Free sample · answers included

Free Salesforce Platform Developer II
Practice Questions

18 free Platform Developer II practice questions from our bank of 300 reviewed questions, picked across all 5 official exam sections in proportion to their weight. Each one shows the correct answer, why it is right and why the others are wrong, and the official documentation it was checked against.

Last updated

What is the Platform Developer II exam like?

60+5

Scored + unscored questions

120 min

Time limit

70%

Passing score

$200

Registration (retake $100)

Confirm current details in the official Platform Developer II exam guide.

Questions by exam section

Heavier sections get more questions, the same way the real exam and our 60-question practice exams are weighted.

Advanced Developer Fundamentals

15% of the exam
  1. Question 1 · Medium · Choose 2

    Northern Trail Outfitters shares each Expedition__c record, whose organization-wide default is Private, with every member of the expedition's crew. The current trigger inserts one Expedition__Share row for each crew member on each of 60,000 expeditions. Crews have up to 300 members, people change crews daily, and every change inserts or deletes thousands of share rows. The developer plans to create one public group for each crew. Which two steps should the new design include? Choose 2 answers.
    • A.Insert one Expedition__Share row per expedition, with the crew's group ID in UserOrGroupId and an Apex sharing reason in RowCause.
    • B.Set AccessLevel to All on each group share so that crew members can also transfer, share, and delete the expeditions they work on.
    • C.Have Apex create a criteria-based sharing rule for each new crew so that the platform maintains the group shares automatically.
    • D.When a person joins or leaves a crew, insert or delete that user's GroupMember record instead of changing Expedition__Share rows.
    • E.Update the UserOrGroupId of each existing Expedition__Share row to the new group's ID instead of inserting new share rows.
    Show the answer and explanation
    Correct answers: A. Insert one Expedition__Share row per expedition, with the crew's group ID in UserOrGroupId and an Apex sharing reason in RowCause.; D. When a person joins or leaves a crew, insert or delete that user's GroupMember record instead of changing Expedition__Share rows.
    A share row's UserOrGroupId can reference a public group, so sharing each expedition once with the crew's group, using an Apex sharing reason, replaces hundreds of per-user rows. Crew changes then only add or remove GroupMember records, which represent the members of a public group, and the expedition shares stay the same; because GroupMember is a setup object, that DML must run in a separate transaction from DML on records such as Expedition__c. The All access level can't be granted through Apex. Apex can't create criteria-based sharing rules. UserOrGroupId can't be updated on an existing share row, so old rows must be deleted and new ones inserted.
  2. Question 2 · Medium · Choose 2

    Universal Containers has multiple currencies enabled. A developer at Universal Containers reviews this query, which runs for a user whose personal currency is EUR:
    SELECT Name, Amount, convertCurrency(Amount) convertedAmount
    FROM Opportunity
    WHERE Amount > USD50000
    Which two statements about the query are true? Choose 2 answers.
    • A.Amount is returned in the org's corporate currency.
    • B.The query fails because the result of convertCurrency() can't be given an alias.
    • C.convertedAmount contains each opportunity's amount converted to the user's currency, EUR.
    • D.The filter returns opportunities whose amount is greater than the equivalent of 50,000 US dollars, whatever each record's currency is.
    • E.The filter compares the number 50,000 to each amount without taking the record's currency into account.
    Show the answer and explanation
    Correct answers: C. convertedAmount contains each opportunity's amount converted to the user's currency, EUR.; D. The filter returns opportunities whose amount is greater than the equivalent of 50,000 US dollars, whatever each record's currency is.
    convertCurrency() in the SELECT clause converts currency fields to the user's currency, and it supports an alias such as convertedAmount. In the WHERE clause, convertCurrency() isn't allowed, so SOQL uses an ISO-code prefix: Amount > USD50000 returns records whose amount is greater than the equivalent of USD 50,000. Only a filter written without the ISO code compares the plain number 50,000 to each amount regardless of the record's currency, so the statement that the filter ignores the record's currency is wrong. The unwrapped Amount field comes back in the record's own currency, not the corporate currency, and aliasing convertCurrency() doesn't cause an error.
  3. Question 3 · Harder

    Ursa Major Solar's Lightning web component lists the service regions stored in the Service_Region__mdt custom metadata type. It calls this Apex method:
    public with sharing class RegionController {
        @AuraEnabled(cacheable=true)
        public static List<Service_Region__mdt> getRegions() {
            return [SELECT DeveloperName, Label, Region_Code__c FROM Service_Region__mdt];
        }
    }
    The org uses the default Schema Settings. The component shows the regions to administrators, but it doesn't work for field technicians. The security team doesn't want to grant technicians access to the custom metadata type. What should the developer do?
    • A.Declare the class without sharing so that the query runs in system mode and returns the records to every user
    • B.Add WITH SYSTEM_MODE to the query so that the technicians' access to the custom metadata type isn't checked
    • C.Copy the field values into a wrapper class or a list of strings, and return that instead of the records
    • D.Replace the query with Service_Region__mdt.getAll().values(), which doesn't count against SOQL limits
    Show the answer and explanation
    Correct answer: C. Copy the field values into a wrapper class or a list of strings, and return that instead of the records
    The Restrict access to custom metadata types preference is enabled by default, so users need access granted through profiles or permission sets, and functionality that runs in user mode, such as an Aura or Lightning component receiving Service_Region__mdt records from an @AuraEnabled method, checks that access. Apex runs in system mode and isn't affected by the preference, so returning the values in a wrapper class or as strings works, because the result is no longer the custom metadata type. The with sharing keyword doesn't affect access to custom metadata types, and the query already runs in system mode, so neither without sharing nor WITH SYSTEM_MODE changes what the component receives. getAll() still returns Service_Region__mdt records, so the same access check applies.

Process Automation, Logic, and Integration

27% of the exam
  1. Question 4 · Medium

    AW Computing sells a managed package that calculates shipping costs. Subscribers can supply their own calculation logic by writing a global class that implements the package's global ShippingCalculator interface, and they enter the class name in a custom metadata record. How should the package instantiate the subscriber's class at run time?
    • A.Call Schema.getGlobalDescribe().get(className) and cast the result to ShippingCalculator.
    • B.Get the class's type with Type.forName, then call newInstance() and cast the result to ShippingCalculator.
    • C.Query the ApexClass object for the class body and run it with Database.query.
    • D.Use dynamic DML to insert a record of the subscriber class type and cast it to ShippingCalculator.
    Show the answer and explanation
    Correct answer: B. Get the class's type with Type.forName, then call newInstance() and cast the result to ShippingCalculator.
    Type.forName returns the System.Type for a public or global class name, and newInstance() creates an instance that can be cast to the interface. Salesforce documents this pattern for packages that read the name of an implementation class from the subscriber's configuration. From an installed managed package, use the two-argument Type.forName(namespace, name) with an empty or null namespace when the subscriber org has no namespace, because the one-argument form returns null there. Schema.getGlobalDescribe() returns only sObject tokens, not Apex classes. Querying ApexClass returns metadata about a class, and Database.query runs SOQL, not Apex code. Dynamic DML creates and saves sObject records and can't instantiate Apex classes.
  2. Question 5 · Harder

    Refer to the query below:
    SELECT LeadSource, COUNT(Id) cnt
    FROM Lead
    GROUP BY ROLLUP(LeadSource)
    A developer at Northern Trail Outfitters loops through the AggregateResult rows to build a chart that must include leads with no lead source. Two rows have a null LeadSource: one for leads with no lead source and one grand total. How should the developer identify the grand-total row?
    • A.Add GROUPING(LeadSource) to the SELECT list and treat the row where it returns 1 as the grand-total row.
    • B.Change the clause to GROUP BY CUBE(LeadSource), which labels subtotal rows with the text Total.
    • C.Add WHERE LeadSource != null so that only the grand-total row has a null LeadSource.
    • D.Treat the first row returned as the grand total, because ROLLUP always returns subtotal rows first.
    Show the answer and explanation
    Correct answer: A. Add GROUPING(LeadSource) to the SELECT list and treat the row where it returns 1 as the grand-total row.
    GROUP BY ROLLUP adds subtotal rows whose grouping field is null, and GROUPING(fieldName) returns 1 when a row is a subtotal for that field and 0 otherwise, which Salesforce describes as the best way to differentiate aggregated data from subtotals. GROUP BY CUBE also returns subtotal rows with null grouping values; it doesn't add a Total label. Filtering out null LeadSource values in WHERE removes the leads with no lead source from the counts, which breaks the chart requirement and changes the grand total. Result order isn't guaranteed without ORDER BY, and in the documented examples the grand total appears as the last row, not the first.
  3. Question 6 · Medium

    A mobile app at Cloud Kicks must send one HTTP request to Salesforce that creates a Store_Visit__c record, creates a Visit_Note__c record that looks up to the new visit, and updates the existing Account record's Last_Visit__c lookup with the new visit's ID. If any step fails, none of the changes can be saved, and the team doesn't want to write custom Apex. Which REST API approach should the developer use?
    • A.The Composite Batch resource with haltOnError set to true and one subrequest for each of the three records
    • B.The Composite resource with allOrNone set to true and @{refVisit.id} references in later subrequests
    • C.The sObject Tree resource, with the visit note nested under the visit and the account nested under the note
    • D.The sObject Collections resource, sending all three records in a single request with allOrNone set to true
    Show the answer and explanation
    Correct answer: B. The Composite resource with allOrNone set to true and @{refVisit.id} references in later subrequests
    The Composite resource runs a series of subrequests in one call, and a later subrequest can use the output of an earlier one through its reference ID, such as @{refVisit.id}. With allOrNone set to true, an error in any subrequest rolls back the entire composite request. Composite Batch subrequests are independent and can't pass information to each other, and haltOnError only stops the remaining subrequests; commits made by earlier subrequests aren't rolled back. sObject Tree creates new parent-child record trees, so it can't update the existing account. An sObject Collections request performs one kind of operation, such as create with POST or update with PATCH, so it can't create the visit and update the account together.
  4. Question 7 · Medium

    At Universal Containers, an after insert trigger on Order publishes an Order_Placed__e platform event, which uses the default publish behavior. Sometimes a subscriber can't find the order when it queries for it. Events are also received for orders whose transaction later failed and rolled back. What should the developer do?
    • A.Change the event's publish behavior to Publish After Commit.
    • B.Call Database.setSavepoint() before EventBus.publish() and roll back the event if the order fails.
    • C.Increase the batch size of the subscriber's Apex trigger to 2,000 events.
    • D.Move the EventBus.publish() call from the after insert trigger to a before insert trigger.
    Show the answer and explanation
    Correct answer: A. Change the event's publish behavior to Publish After Commit.
    With Publish Immediately, the default, the event message is published as soon as the publish call runs, whether or not the transaction succeeds. A subscriber can therefore receive it before the order is committed, or for an order that's rolled back. Publish After Commit publishes the message only after the transaction commits successfully, and doesn't publish it if the transaction fails. A savepoint can't undo a Publish Immediately message, because the message is published when the publish call runs, regardless of whether the transaction succeeds. The subscriber's batch size doesn't affect when messages are published. A before insert trigger runs in the same uncommitted transaction, so moving the publish call there doesn't fix the timing.
  5. Question 8 · Medium · Choose 2

    Get Cloudy Consulting has one large record-triggered flow on Shipment__c with loops, collection filters, and cross-object updates. During nightly Bulk API loads of 10,000 records, the flow hits CPU time limits. The architect proposes moving this logic to an Apex trigger framework. Which two considerations support the proposal? Choose 2 answers.
    • A.Apex offers Map and Set collections and finer control over queries, which make bulk processing more efficient than Flow loops.
    • B.Apex can cache expensive query results in static variables and reuse them across invocations in the same transaction.
    • C.Splitting the flow into several smaller, well-conditioned flows would remove the CPU time problem without any code.
    • D.Apex triggers are exempt from governor limits because Apex code in triggers runs in system mode.
    • E.Record-triggered flows can't process more than 200 records from a single Bulk API load.
    Show the answer and explanation
    Correct answers: A. Apex offers Map and Set collections and finer control over queries, which make bulk processing more efficient than Flow loops.; B. Apex can cache expensive query results in static variables and reuse them across invocations in the same transaction.
    Salesforce's automation guidance rates Apex as the recommended tool for high-performance bulk processing because it provides Map and Set data structures and more control over query optimization, while Flow lacks native maps and sets. Apex can also deduplicate expensive work by caching data in static variables for the duration of the transaction, something Flow can't do across invocations. Splitting a mega-flow into multiple flows doesn't have a major performance impact; the gains come from before-save updates and precise entry conditions. Apex in triggers is always subject to governor limits; system mode concerns data access, not limits. Flows do run for Bulk API loads, which are processed in chunks rather than rejected.

User Interface

20% of the exam
  1. Question 9 · Harder

    Northern Trail Outfitters has a Lightning web component that shows quote line items and lets a sales rep apply a discount. getLineItems is a cacheable Apex method, and applyDiscount updates the line items. Refer to the component's JavaScript:
    import { refreshApex } from '@salesforce/apex';
    // getLineItems and applyDiscount are imported from QuoteController
    export default class QuoteLines extends LightningElement {
        @api recordId;
        lineItems;
        @wire(getLineItems, { quoteId: '$recordId' })
        wiredLines({ data }) {
            if (data) { this.lineItems = data; }
        }
        async handleDiscount() {
            await applyDiscount({ quoteId: this.recordId });
            await refreshApex(this.lineItems);
        }
    }
    The discount is saved, but the component keeps showing the old prices until the page is reloaded. What should the developer do?
    • A.Add cacheable=true to applyDiscount so that both methods share the same client-side cache.
    • B.Replace the refreshApex() call with notifyRecordUpdateAvailable() for the quote record.
    • C.Store the value the wire service passes to wiredLines in a property, then pass that property to refreshApex().
    • D.Decorate lineItems with @track so the component rerenders when the discount is applied.
    Show the answer and explanation
    Correct answer: C. Store the value the wire service passes to wiredLines in a property, then pass that property to refreshApex().
    refreshApex() must receive an object that an Apex @wire previously emitted; for a wired function, that's the argument the function receives, not the data extracted from it. Saving that value (for example, this.wiredResult = result) and calling refreshApex(this.wiredResult) makes the wire requery the server and provision fresh line items. applyDiscount changes data, so it can't be marked cacheable, and caching it wouldn't refresh another method's results anyway. notifyRecordUpdateAvailable() refreshes records in the Lightning Data Service cache, but Lightning Data Service doesn't manage data provisioned by Apex, so the wired line items would stay stale. @track only affects how changes to a field are observed; the real problem is that no new data is requested.
  2. Question 10 · Harder

    In a Lightning console app at Cumulus Financial, a Lightning web component on a case workspace subtab subscribes to a message channel:
    @wire(MessageContext)
    messageContext;
    
    connectedCallback() {
        if (!this.subscription) {
            this.subscription = subscribe(
                this.messageContext,
                CASE_ALERT_CHANNEL,
                (message) => this.handleAlert(message)
            );
        }
    }
    The component receives messages only while its subtab is the one the agent is viewing. It must receive alerts even when the agent is working in another tab. What should the developer do?
    • A.Pass { scope: APPLICATION_SCOPE }, imported from lightning/messageService, as the fourth argument to subscribe().
    • B.Pass { scope: APPLICATION_SCOPE } as the fourth argument to publish() in the publishing component.
    • C.Replace @wire(MessageContext) with createMessageContext() so the subscription isn't tied to the active tab.
    • D.Move the subscribe() call into the constructor so the subscription exists before the subtab renders.
    Show the answer and explanation
    Correct answer: A. Pass { scope: APPLICATION_SCOPE }, imported from lightning/messageService, as the fourth argument to subscribe().
    By default, a Lightning message service subscriber receives messages only in the application's active area, such as the selected navigation or workspace tabs and utility items. To receive messages from anywhere in the application, import APPLICATION_SCOPE from lightning/messageService and pass { scope: APPLICATION_SCOPE } as the optional fourth subscriberOptions argument to subscribe(). Scope is a subscriber option, so passing it to publish() doesn't change which subscribers receive the message. For Lightning web components, scoping is available only with @wire(MessageContext), and createMessageContext() is for API modules that don't extend LightningElement. A component must be attached to the DOM before it can use the MessageContext property, so subscribing in the constructor is too early.
  3. Question 11 · Harder

    A developer at Get Cloudy Consulting places eight project tiles inside <lightning-layout multiple-rows>. Each tile is wrapped in this layout item:
    <lightning-layout-item size="12" small-device-size="6" large-device-size="3">
        <c-project-tile></c-project-tile>
    </lightning-layout-item>
    How wide is each tile when a user views the component on a tablet?
    • A.Full width, because size="12" applies to every device type.
    • B.One quarter of the width, because large-device-size applies to tablets and larger devices.
    • C.The width of its content, because the device-size attributes are ignored when medium-device-size isn't set.
    • D.Half the width, because small-device-size applies to devices of that size and larger, and medium-device-size isn't set.
    Show the answer and explanation
    Correct answer: D. Half the width, because small-device-size applies to devices of that size and larger, and medium-device-size isn't set.
    For lightning-layout-item, small-device-size typically targets smart phones, medium-device-size targets tablets, and large-device-size targets desktops or larger devices. The device-size attributes are additive and apply to devices of the specified size and larger, so with no medium-device-size, a tablet uses the small-device-size value of 6 out of 12 columns, which is half the width. When both size and small-device-size are set, size applies only to small mobile phones. large-device-size applies to desktops and larger, not tablets. A missing medium-device-size doesn't make the item fall back to its content width; the smaller device setting carries up.
  4. Question 12 · Medium

    Refer to this Aura component at Northern Trail Outfitters:
    <aura:component implements="flexipage:availableForRecordHome">
        <ltng:require scripts="{!$Resource.mapLib + '/leaflet.js'}"
                      styles="{!$Resource.mapLib + '/leaflet.css'}"/>
        <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
        <div aura:id="map" class="trail-map"></div>
    </aura:component>
    The doInit action creates the map with the library's global L object. Users intermittently see an error that L is undefined. What should the developer do?
    • A.Replace $Resource.mapLib + '/leaflet.js' with URLFOR($Resource.mapLib, 'leaflet.js') in the scripts attribute.
    • B.Add a <script> tag that references the static resource directly in the component markup.
    • C.Create the map in an action set by afterScriptsLoaded="{!c.initMap}" on ltng:require instead of in doInit.
    • D.Load the library in doInit by calling $A.get('$Resource.mapLib') before the code that creates the map.
    Show the answer and explanation
    Correct answer: C. Create the map in an action set by afterScriptsLoaded="{!c.initMap}" on ltng:require instead of in doInit.
    Scripts loaded with ltng:require load asynchronously, so they probably aren't available yet when the init handler runs. The afterScriptsLoaded action runs after the scripts are loaded and the component is rendered, so the map should be created there. Aura has nothing like URLFOR(); string concatenation with $Resource, as the component already uses, is the correct syntax. You can't use a <script> tag in an Aura component, a restriction that mitigates the risk of cross-site scripting attacks. $A.get('$Resource.mapLib') only returns the resource's URL; it doesn't load or run the script.

Testing, Debugging, and Deployment

20% of the exam
  1. Question 13 · Medium

    A developer at Universal Containers wrote this Jest test for a c-greeting component. Its template renders <p>Hello, {name}</p>, and its name property is decorated with @api and defaults to 'World'.
    it('greets the user by name', () => {
        const element = createElement('c-greeting', { is: Greeting });
        document.body.appendChild(element);
        element.name = 'Ada';
        const p = element.shadowRoot.querySelector('p');
        expect(p.textContent).toBe('Hello, Ada');
    });
    The test fails because the paragraph still reads "Hello, World". Which change fixes the test?
    • A.Query the paragraph with document.querySelector('p') instead of element.shadowRoot.querySelector('p').
    • B.Make the test function async and add await Promise.resolve(); after setting element.name, before querying the paragraph.
    • C.Call element.connectedCallback() after setting element.name to force the component to rerender.
    • D.Decorate name with @track instead of @api so the template updates synchronously.
    Show the answer and explanation
    Correct answer: B. Make the test function async and add await Promise.resolve(); after setting element.name, before querying the paragraph.
    When a component's state changes after it's inserted into the DOM, the DOM updates asynchronously, so the test must wait for a resolved promise before checking the result. Setting the property before appendChild() would also work, because the component then renders synchronously. document.querySelector() can't see into the component's shadow tree; element.shadowRoot is the test-only API for inspecting it, so that change returns null. Tests don't call lifecycle hooks to force a rerender. Replacing @api with @track would remove name from the component's public API, and rerendering would still be asynchronous.
  2. Question 14 · Harder

    At Northern Trail Outfitters, Shipment_Line__c is the detail object in a master-detail relationship with Shipment__c. A before delete trigger on Shipment_Line__c creates an Archive__c record for each deleted line. Archive records are created when users delete individual lines, but not when a user deletes a whole shipment, even though its lines are deleted too. What is the cause?
    • A.Cascading deletes don't invoke triggers on the child records, because only records that initiate a delete cause trigger evaluation; the archiving must also be handled by a delete trigger on Shipment__c.
    • B.Before delete triggers on detail objects run only for records deleted through the API, not for deletions made in the user interface.
    • C.The Shipment_Line__c trigger runs, but Archive__c records inserted during a cascade delete are rolled back when the master record is deleted.
    • D.The trigger must be changed from before delete to after delete, because before delete triggers never run when a parent record's deletion cascades to its children, but after delete triggers do run for the cascaded deletion in that case.
    Show the answer and explanation
    Correct answer: A. Cascading deletes don't invoke triggers on the child records, because only records that initiate a delete cause trigger evaluation; the archiving must also be handled by a delete trigger on Shipment__c.
    Some system operations don't invoke triggers, and cascading deletes are one of them: only the records that initiate a delete cause trigger evaluation. When a user deletes a Shipment__c record, its Shipment_Line__c records are removed by the cascade, so no Shipment_Line__c trigger runs. A before delete trigger on Shipment__c can query the lines before they're removed and archive them. The trigger doesn't depend on whether a direct delete came from the API or the UI, no Archive__c records are rolled back because the trigger never runs, and switching to after delete doesn't help for the same reason.
  3. Question 15 · Easier

    Ursa Major Solar's IncentiveCalculator class has a private static method, tierFor(Decimal kilowatts), that contains complex branching logic. A developer wants a unit test to call this method directly, but the method must stay inaccessible to other production classes. What should the developer do?
    • A.Change the method's access modifier to public and add a comment that it's for testing only.
    • B.Annotate the test class with @IsTest(SeeAllData=true) so it can call private methods.
    • C.Annotate the method with @TestVisible.
    • D.Declare IncentiveCalculator as global so that test classes can access its private members.
    Show the answer and explanation
    Correct answer: C. Annotate the method with @TestVisible.
    @TestVisible lets test methods access private or protected members of another class, including methods, member variables, and inner classes, and it doesn't change their visibility for non-test code. Making the method public exposes it to every class that can see IncentiveCalculator, which breaks the requirement. SeeAllData=true controls access to org data, not access to class members. Declaring the class global widens access to the class itself but doesn't expose its private members.

Performance

18% of the exam
  1. Question 16 · Harder

    The Delivery__c object at Northern Trail Outfitters holds 4 million records. A developer must pick one filter for a query that runs in a trigger, and the query must be selective. Based on the query optimizer's index thresholds, which filter is selective?
    • A.WHERE Region__c = 'West', where Region__c has a custom index and the filter matches 350,000 records
    • B.WHERE CreatedDate = LAST_N_DAYS:90, which matches 700,000 records
    • C.WHERE Tracking_Code__c = :code, where Tracking_Code__c is a custom text field without an index and the filter matches 5 records
    • D.WHERE Status__c != 'Delivered', where Status__c has a custom index and the filter matches 100,000 records
    Show the answer and explanation
    Correct answer: B. WHERE CreatedDate = LAST_N_DAYS:90, which matches 700,000 records
    A filter is selective only when it's on an indexed field, uses an operator the index supports, and matches fewer records than the index threshold. For a standard index such as CreatedDate, the threshold is 30% of the first million records plus 15% of the remaining records, up to 1 million; for 4 million records that's 750,000, so a filter that matches 700,000 records qualifies. A custom index is used only when the filter matches less than 10% of the records, up to a maximum of 333,333, so 350,000 matches are too many. A filter on an unindexed field isn't selective however few records it matches, because the optimizer has no index to use. A custom index typically isn't used with a negative operator such as !=.
  2. Question 17 · Easier

    A before insert trigger handler at Cloud Kicks sets a field on each new Order__c record based on the running user's role:
    for (Order__c o : orders) {
        User u = [SELECT UserRoleId FROM User WHERE Id = :UserInfo.getUserId()];
        o.Created_By_Role__c = u.UserRoleId;
    }
    Imports of more than 100 orders fail with System.LimitException: Too many SOQL queries: 101. Which change removes the query from the transaction entirely, rather than just moving it outside the loop?
    • A.Add LIMIT 1 to the query so that each iteration retrieves only one row.
    • B.Move the query into a @future method so that it runs with higher governor limits.
    • C.Query all users with SELECT Id, UserRoleId FROM User before the loop, and search the list for the running user.
    • D.Call UserInfo.getUserRoleId() once before the loop, and assign the stored value inside the loop.
    Show the answer and explanation
    Correct answer: D. Call UserInfo.getUserRoleId() once before the loop, and assign the stored value inside the loop.
    The query returns the same value for every order, yet it runs once per record. UserInfo.getUserRoleId() returns the context user's role ID without a SOQL query, and calling it once before the loop removes the repeated work entirely. LIMIT 1 still runs one query per order, so the limit is still exceeded. A future method runs later in a separate transaction, so it can't supply the value before the orders are saved. Querying every user retrieves many unnecessary rows and adds a search through the list, when the value is already available without any query.
  3. Question 18 · Medium

    A developer at Get Cloudy Consulting built a Lightning web component for the Project__c record page that shows the project's status, budget, and end date. The component uses this markup:
    <lightning-record-form
        record-id={recordId}
        object-api-name="Project__c"
        layout-type="Full"
        mode="readonly">
    </lightning-record-form>
    The Project__c page layout contains more than 150 fields, and the component loads slowly. What should the developer do?
    • A.Replace the component with an Apex method that queries every Project__c field.
    • B.Replace layout-type with a fields attribute that lists only the three fields the component displays.
    • C.Change mode to "view" so that the form loads in view mode.
    • D.Wrap the form in a <template lwc:if> whose condition is set to true in renderedCallback().
    Show the answer and explanation
    Correct answer: B. Replace layout-type with a fields attribute that lists only the three fields the component displays.
    Salesforce recommends specifying fields instead of a layout whenever possible to improve performance, because a layout makes the component load every field that the administrator assigns to it. Listing only the three fields in the fields attribute reduces the data that the form requests and renders. Querying every field in Apex moves the same problem to the server and gives up Lightning Data Service caching. Changing the mode controls whether users can edit fields inline, not how many fields load. Rendering the form after renderedCallback() only delays it and triggers an extra render.

Where to go after these 18 questions

Ready for a full Platform Developer II practice exam?

Take a timed 60-question exam drawn from all 300 reviewed questions in the real exam's section mix, then drill the sections you miss.