12.2 Expressions, Control Flow Statements & Built-in Functions

Key Takeaways

  • AL supports full arithmetic, relational, and boolean logical operators, including DIV for integer division truncation, MOD for remainder calculation, and short-circuit boolean evaluation.
  • In if...then...else statements, placing a semicolon after then or before else terminates the conditional statement prematurely and triggers compiler syntax errors.
  • repeat...until loops evaluate conditions at the end and are guaranteed to execute at least once, making them the standard pattern for database recordset traversal when guarded by if FindSet().
  • String manipulation functions (StrSubstNo, CopyStr with MaxStrLen, DelChr, PadStr, IncStr) provide robust data sanitization and prevent string length overflow runtime crashes.
  • CalcDate uses language-invariant date formulas enclosed in angle brackets (<...>) to calculate dynamic fiscal and calendar intervals relative to WorkDate() or Today().
Last updated: August 2026

12.2 Expressions, Control Flow Statements & Built-in Functions

Control flow statements and built-in system functions form the procedural backbone of business logic in Business Central. AL provides structured programming constructs for conditional branching, deterministic and non-deterministic iteration, string parsing, mathematical calculation, and temporal date calculations. For the MB-820 exam, developers must thoroughly understand statement execution rules, syntax pitfalls (such as semicolon placement in if-then-else blocks), loop execution guarantees, and standard built-in functions.


1. AL Operators & Expression Evaluation

AL expressions combine operands (variables, constants, literals, and function returns) with operators to yield a computed value.

+---------------------------------------------------------------------------------------------------+
|                                      AL OPERATORS TAXONOMY                                        |
+-------------------+--------------------+----------------------------------------------------------+
| Category          | Operators          | Operational Behavior & Notes                             |
+-------------------+--------------------+----------------------------------------------------------+
| Arithmetic        | + , - , *          | Standard addition, subtraction, multiplication           |
| Division          | /                  | Floating-point / Decimal division (e.g., 7 / 2 = 3.5)    |
|                   | DIV                | Integer division truncation (e.g., 7 DIV 2 = 3)          |
|                   | MOD                | Integer modulus remainder (e.g., 7 MOD 2 = 1)            |
| Relational        | = , <>             | Equal to, Not equal to                                   |
|                   | < , <= , > , >=    | Less than, Less than or equal, Greater than, Greater eq. |
| Set Membership    | in                 | Evaluates if value exists in a set [1..5, 10]            |
| Logical (Boolean) | and , or , not     | Short-circuit logical conjunction, disjunction, negation |
|                   | xor                | Exclusive OR logical evaluation                          |
| Assignment        | :=                 | Standard assignment                                      |
| Compound Assign   | += , -= , *= , /=  | In-place arithmetic assignment (e.g., Total += LineTotal)|
| String Concatenat.| +                  | Concatenates text and code strings                       |
+-------------------+--------------------+----------------------------------------------------------+

Division Operators: / vs. DIV vs. MOD

  • Decimal Division (/): Returns a high-precision Decimal result. When dividing integers (17 / 5), the result is implicitly promoted to decimal 3.4.
  • Integer Division (DIV): Divides two numeric values and returns only the integer portion of the quotient, truncating any fractional remainder (17 DIV 5 = 3).
  • Modulus (MOD): Returns the integer remainder resulting from division (17 MOD 5 = 2).

2. Conditional Branching: if...then...else & case...of

Conditional logic routes execution paths based on boolean evaluations.

if...then...else Syntax & The Semicolon Rule

// Single statement branch (No begin...end required)
if Customer."Balance (LCY)" > Customer."Credit Limit (LCY)" then
    Customer.Blocked := Customer.Blocked::All
else
    Customer.Blocked := Customer.Blocked::" ";

// Compound block branch (begin...end)
if SalesHeader."Document Type" = SalesHeader."Document Type"::Order then begin
    CheckInventoryAvailability(SalesHeader);
    ReleaseSalesDocument(SalesHeader);
end else begin
    ValidateQuoteTerms(SalesHeader);
end;

Exam Trap — Semicolon Placement in if-then-else: In AL, placing a semicolon immediately after the then keyword or on the statement/end immediately preceding the else keyword terminates the if statement. The compiler will then encounter the else keyword in isolation and throw a syntax error reporting that the else keyword is not expected in that position.

Incorrect: if A > B then; DoWork(); (Semicolon makes DoWork() execute unconditionally!) Incorrect: if A > B then DoWork(); else DoOther(); (Semicolon before else causes compile error!) Correct: if A > B then DoWork() else DoOther();

case...of Statements

The case statement evaluates an expression against a list of matching values or value ranges, providing a clean alternative to deeply nested if-then-else blocks:

local procedure DetermineShippingPriority(DeliveryDays: Integer): Text[20]
begin
    case DeliveryDays of
        0: // Exact single value match
            exit('Same-Day Express');
        1, 2: // Multiple comma-separated values
            exit('Priority Overnight');
        3 .. 5: // Range match (inclusive)
            exit('Standard Ground');
        else // Fallback catch-all
            exit('Economy Freight');
    end;
end;

3. Iteration & Looping Constructs

AL supports four looping constructs designed for specific traversal and iteration scenarios:

// 1. repeat...until (Standard Record Traversal Pattern)
if Customer.FindSet() then
    repeat
        ProcessCustomerAccount(Customer);
    until Customer.Next() = 0;

// 2. while...do (Pre-Condition Check Loop)
while (BufferRemaining > 0) and (not EndOfStream) do begin
    ReadNextChunk();
    BufferRemaining -= 1;
end;

// 3. for...to / for...downto (Counter-Controlled Loops)
for Index := 1 to CustomerList.Count() do begin
    ProcessItemByIndex(Index);
end;

for Counter := 10 downto 1 do begin
    Countdown(Counter);
end;

// 4. foreach...in (Collection & Array Enumeration)
foreach CurrentEmail in EmailRecipientList do begin
    SendNotification(CurrentEmail);
end;

Looping Statement Comparison & Best Practices

Looping ConstructCondition EvaluationExecution GuaranteePrimary Use Case in Business Central
repeat...untilPost-test (at bottom)Guaranteed at least onceNavigating database recordsets (Customer.FindSet() ... until Customer.Next() = 0). Must be guarded by if FindSet() to prevent running on empty sets.
while...doPre-test (at top)Zero or more timesStream reading and processing where loop condition might be false immediately.
for...to / downtoCounter boundFixed number of iterationsTraversing fixed arrays or numeric ranges with deterministic boundaries.
foreach...inCollection enumeratorZero or more timesIterating over in-memory List of [T] collections without managing index pointers.

Loop Control Statements: break and exit

  • break: Immediately terminates the innermost loop (repeat, while, for, foreach) and resumes execution at the statement following the loop.
  • exit: Immediately exits the active procedure or trigger. If the procedure declares a return value, exit(ReturnValue) returns the specified result to the calling procedure.
Loading diagram...
AL Control Flow & Record Traversal Lifecycle

4. Built-in String Manipulation Functions

String manipulation functions in AL are essential for preparing user-facing messages, sanitizing input data, and constructing structured payloads.

FunctionSyntax SignatureDescription & Practical Example
StrSubstNoStrSubstNo(FormatString, [Val1], ...)Replaces placeholders (%1, %2, etc.) with string representations of values.<br/>Msg := StrSubstNo('Invoice %1 total is %2', InvNo, Amount);
FormatFormat(Value, [Length], [FormatNumber])Converts any data type (Date, Decimal, Enum) to formatted Text.<br/>DateText := Format(Today(), 0, '<Day,2>/<Month,2>/<Year4>');
CopyStrCopyStr(String, Position, [Length])Extracts a substring starting at 1-based Position. Use MaxStrLen to prevent overflow:<br/>CustName20 := CopyStr(LongName, 1, MaxStrLen(CustName20));
StrLenStrLen(String)Returns the integer character count of the specified string.<br/>Len := StrLen(Customer.Name);
LowerCaseLowerCase(String)Converts all characters in the string to lowercase.<br/>Email := LowerCase(RawEmail);
UpperCaseUpperCase(String)Converts all characters in the string to uppercase.<br/>CleanCode := UpperCase(InputText);
DelChrDelChr(String, [Where], [Which])Deletes characters specified in Which. Where flags: '=' (all occurrences), '<' (leading only), '>' (trailing only), '<>' (leading and trailing).<br/>CleanPhone := DelChr(Phone, '=', ' -()');
PadStrPadStr(String, Length, [FillChar])Appends or truncates the string with FillChar to reach exact Length.<br/>Padded := PadStr('123', 6, '0'); // '123000'
StrPosStrPos(String, SubString)Returns the 1-based integer index of the first occurrence of SubString, or 0 if not found.<br/>AtPos := StrPos(Email, '@');
IncStrIncStr(String)Increments the rightmost positive number inside a string identifier.<br/>NextNo := IncStr('SO-0099'); // Returns 'SO-0100'
SelectStrSelectStr(Index, CommaSeparatedString)Retrieves the 1-based nth comma-separated token from a delimited string.<br/>Token := SelectStr(2, 'Red,Green,Blue'); // 'Green'

Exam Watchout — Safe String Assignment with CopyStr: Assigning a longer Text variable to a shorter table field (e.g., assigning a 100-character description to a Text[50] field) causes a fatal runtime error: "The length of the string is X, but it must be less than or equal to 50 characters." To avoid runtime crashes, always guard truncation using CopyStr(SourceText, 1, MaxStrLen(TargetField)).

5. Built-in Date and Time Functions

Business Central applications rely heavily on accurate accounting dates, fiscal periods, and posting validations.

Core System Date Functions

  • Today(): Returns the current system date from the host server/client operating system.
  • Time(): Returns the current system time.
  • CurrentDateTime(): Returns the current UTC DateTime timestamp.
  • WorkDate(): Returns or sets the user's active accounting work date configured in My Settings. In Business Central business logic, default transaction and posting dates must always use WorkDate() rather than Today() so accounting users can post into historical or future open fiscal periods.

Dynamic Date Calculation: CalcDate

The CalcDate function computes a target date by applying a DateFormula expression to a reference date. If no reference date is specified, it calculates against Today().

local procedure CalculatePaymentDueDates(BaseDate: Date)
var
    DueDate: Date;
    EndOfCurrentMonth: Date;
    NextQuarterStart: Date;
begin
    // Add 30 calendar days
    DueDate := CalcDate('<+30D>', BaseDate);

    // Current Month end (CM) plus 10 days
    DueDate := CalcDate('<CM+10D>', BaseDate);

    // Current Quarter end (CQ) plus 1 day to find start of next quarter
    NextQuarterStart := CalcDate('<CQ+1D>', BaseDate);

    // Current Year start (-CY)
    DueDate := CalcDate('<-CY>', BaseDate);
end;

DateFormula Syntax Codes

  • D (Day): +10D (add 10 days), -5D (subtract 5 days).
  • W (Week): +2W (add 2 weeks).
  • M (Month): +1M (add 1 calendar month), -3M (subtract 3 months).
  • Q (Quarter): +1Q (add 1 financial quarter).
  • Y (Year): +1Y (add 1 calendar year).
  • C (Current): Prefixed before a time unit to designate the end of the current period (CM = end of current month, CQ = end of current quarter, CY = end of current year). Prefixed with a minus sign (-CM, -CY) to designate the beginning of the period.
  • Enclosing in Angle Brackets (<...>): Ensures the date formula is language-invariant and compiles regardless of the tenant's localized language (e.g., using <+1M> instead of localized +1M).

Date Deconstruction & Reconstruction

  • Date2DMY(Date, What): Extracts the Day (1), Month (2), or Year (3) as an integer (e.g., MonthInt := Date2DMY(WorkDate(), 2);).
  • DMY2Date(Day, [Month], [Year]): Constructs a valid Date value from day, month, and year integers (e.g., FirstDay := DMY2Date(1, 1, 2026);).
  • Date2DWY(Date, What): Extracts Day of the week (1 = Monday to 7 = Sunday), Week number (2), or Year (3).
  • ClosingDate(Date) vs. NormalDate(Date): Converts a normal date to a special closing date (C12/31/2026) used exclusively in financial close entries, and vice versa.
Test Your Knowledge

A developer writes the following AL code to evaluate a customer's credit status: if Customer."Credit Limit (LCY)" > 100000 then; Message('VIP Customer') else Message('Standard Customer'); What occurs when the developer attempts to compile this AL code?

A
B
C
D
Test Your Knowledge

An accounting transaction is posted on March 15, 2026. The payment terms specify a calculation formula of '<CM+10D>'. What is the calculated due date returned by CalcDate('<CM+10D>', 2026-03-15D)?

A
B
C
D
Test Your Knowledge

A developer executes the following AL expressions with integer variables: A := 17; B := 5; Q := A DIV B; R := A MOD B; D := A / B;. What are the resulting values stored in Q, R, and D?

A
B
C
D
Test Your Knowledge

An AL developer processes telephone strings using the built-in function call: CleanPhone := DelChr(RawPhone, '=', ' -()');. Given the raw input string ' (555) 019-2834 ', what is the exact string returned by this function?

A
B
C
D