Approver Chain Rule: Ten Queries and a Missing Manager
An expression rule getApproverChain(userId) walks up the manager hierarchy using the Employee record type. It uses a!forEach over a hardcoded range(1,10) and calls a!queryRecordType inside the loop to fetch the next manager; each call averages 600 ms, so the rule takes about 6 seconds and the interface occasionally hits its 10-second limit. For a contractor with nine levels it returns only four entries. For an employee whose manager field is null, the form errors out. A developer suggests making the rule call itself recursively to remove the hardcoded 10.
1. Is self-recursion a good fix for the hardcoded range? Justify your answer.
Read the worked answer
No - and it fails for a reason that has nothing to do with performance. A custom expression rule cannot reference itself. The reference is a circular dependency, it is rejected when the rule is saved or evaluated, so the 'fix' does not compile, let alone run faster.
Even if it were permitted, recursion would not solve the real problem. The rule is slow because it issues ten sequential queries of about 600 ms each, and recursion would issue the same number of queries, just with less readable termination logic. It also converts a bounded cost into an unbounded one: a data error such as a manager cycle (A reports to B, B reports to A) would recurse until a platform limit stops it, producing a confusing failure instead of a visible wrong answer.
The hardcoded range(1,10) is a symptom of the real constraint: you cannot fetch an arbitrary-depth hierarchy with per-level queries. The fix belongs in the query pattern or the data model, not in the control flow.
2. Give two ways to get the chain in far fewer queries, with the trade-offs.
Read the worked answer
Option A - one bulk query, walk in memory. Query the Employee record type once for id, managerId, displayName (or for the relevant org), build an id -> managerId map in a!localVariables, then walk the chain with an accumulator (a!reduce over a bounded index list, appending the manager while it is non-null). One query, deterministic, and it terminates cleanly on a null manager.
Trade-off: the cost moves from N round trips to one large result set. That is fine for a few thousand employees and unacceptable for a global HR table, and the data is only as fresh as the query.
Option B - push the traversal to the database. Create a view using a recursive CTE that returns one row per (employee, level, manager) pair, and expose it as a record type. The rule becomes a single filtered query for the employee ID. Sub-second, arbitrary depth, filterable and sortable.
Trade-off: the logic now lives in database-specific SQL, so it must be maintained wherever the schema is deployed, and the view's refresh latency becomes your staleness window.
Choose A when the hierarchy is modest and you want to keep logic in Appian; choose B when depth and volume are real, which the 6-second timings suggest they are.
3. Fix the failure when an employee's manager field is null, and explain how the rule should terminate.
Read the worked answer
The error is the empty-list access again: the loop fetches 'the manager' and then indexes into a result set of size zero, or passes a null ID into the next query. Both need to be treated as normal termination, not as an error.
Guard the data access. Skip the query (or a!isNullOrEmpty the ID) when the manager ID is null, and use index(list, 1, null) rather than first() so an empty result yields null instead of an exception. Null-safe indexing turns a crash into a value you can branch on.
Terminate deliberately. a!forEach cannot break out early, so do not try to 'stop' the loop - instead accumulate with a condition: append the manager only when it is non-null and not already in the chain, then filter the nulls at the end. That also makes a manager cycle harmless (the duplicate check stops the walk) instead of looping until a platform limit throws.
Return a predictable shape. The rule should always return a list, never null - an empty list for an employee with no manager, a single-element list for a top-level manager. Callers then avoid the second failure mode, which is a downstream for each over a null.