[SalesForce] Bulkification of Flow with invocable action – error re: number of results – how to resolve

I'm getting the error "The number of results does not match the number of interviews that were executed in a single bulk execution request" when I insert more than one record into an object that has a record-triggered Flow attached that calls an invocable method to do some data lookup. I'd love some help resolving this; here's the situation:

The real business logic is somewhat complex, but I've created a very simple demonstration scenario so it's easy to reproduce. The goal is to provide a lookup function that returns a list of Strings to a Flow text collection variable when a text value is passed in as an argument. Subsequent elements in the flow determine whether additional records should be created by testing if the collection contains a value in the incoming record. My test scenario simply outputs the return values. OK the setup:

Custom object, "Invoke__c"; two custom fields (text), "Input__c" and "Output__c".

Invocable method:

global with sharing class TestInvocable {
    @InvocableMethod(
            Label='Test invocable'
            Description='Return a list of user last names by passing in a first name.'
            Category='Testing'
    )
    global static List<List<String>> testInvocable(List<String> firstNames) {
        for (String firstName : firstNames) {
            firstName = String.escapeSingleQuotes(firstName);
        }
        List<User> users = [
                SELECT Id, LastName
                FROM User
                WHERE FirstName IN :firstNames
        ];
        List<String> lastNames = new List<String>();
        for (User user : users) {
            lastNames.add(user.LastName);
        }
        return  new List<List<String>>{lastNames};
    }
}

Flow: testInvocableTrigger

enter image description hereThe five elements:

Start: record-triggered, after-save on the Invoke__c object

Action: our Test invocable method; pass in $Record.Input__c to the firstNames argument

Loop: over the variable associated automatically with the action above

Assignment: for each item in the loop, "Add" the current items value to a "stringOfNames" single value text var

Update Records: after last loop item, update the Invoke__c record passed in by spec'ing condition of Id = $Record.Id; update the Output__c field to the stringOfNames var.

Testing this out:
If you insert a single record into the Invoke__c object (specifying a user first name for the Input__c field), it will succeed, and the Output__c field of the incoming record will be updated by the Flow with a concatenation of all the last names for users that match the user first name passed in.
If you insert 2 or more records, the operation will fail with error "The number of results does not match the number of interviews that were executed in a single bulk execution request".

Comments:
I've seen a few posts that mention needing to have "input size and output size match", but no one actually explains what this means, or shows how this is demonstrated in code or in Flow design. It seems unconscionable that we would be restricted to outputting the same number of items from an invocable as the number of records in the batch that get "bulkified" by the Flow engine. That would make any kind of utility function like I propose impossible to implement.

Anyone have suggestions, a fix, or more information about this issue? Thanks very much in-advance. -Ben

Best Answer

In this case, the testInvokable method is combining the results of the soql query into a single list of names. This means it only ever returns one result regardless of how many firstNames are provided.
To get the output you're looking for, you'll want to create a list of last names returned by the query specific to each first name. This ensures that the number of outputs aligns with the number of inputs.

Here's an example

global with sharing class TestInvocable {
@InvocableMethod(
        Label='Test invocable'
        Description='Return a list of user last names by passing in a first name.'
        Category='Testing'
)
global static List<List<String>> testInvocable(List<String> firstNames) {
    Map<String, List<String>> mapLastNamesByFirstName = new Map<String,List<String>>();
    for (String firstName : firstNames) {
        mapLastNamesByFirstName.put(String.escapeSingleQuotes(firstName), new List<String>());
    }
    List<User> users = [
            SELECT Id, FirstName, LastName
            FROM User
            WHERE FirstName IN :firstNames
    ];
    for (User user : users) {
        mapLastNamesByFirstName.get(user.FirstName).add(user.LastName);
    }
    
    List<List<String>> results = new List<List<String>>();
    
    for(String firstName : firstNames) {
        results.add(mapLastNamesByFirstName.get(String.escapeSingleQuotes(firstName)));
    } 

    return results;
}

}