On this page

Approving a batch within available credit

Find the point at which credit changes and the priority between rows. Filtering before validation or sorting rows would change the outcome. See readability under real business rules for policy branches, initialization and coordination.

A service receives available credit and an ordered batch of charges. It accepts a charge when it fits the remaining credit, skips one that does not fit, and continues with the next. Every charge must be positive, including charges that would be declined. The result is the total accepted amount; the input collection is unchanged.

This comparison uses whole credit units represented by signed 32-bit integers. It does not substitute binary floating point for money. The README order example uses Decimal for line amounts; this smaller batch rule isolates iteration, state, validation and error handling across runtimes.

Available credit Charges, in order Result
100 75, 40, 25, 1 100; decline 40, then accept 25
100 80, 80, 20 100
0 1 0
100 Empty collection 0
100 75, 0, 25 Failure; no completed batch total
100 101, -1 Failure; declining the first charge does not skip validation of the second
Integer maximum Integer maximum, 1 Integer maximum, without overflowing

Checking against remaining credit before adding keeps the total within its representation. A failed call does not return a partial successful total. This function has no external side effects or transaction; a service that updates stored accounts must define that separately.

DirectSharp

// Approval policy: preserve authored row order and available-credit updates.
import System
import System.Collections.Generic

public class ApprovalBatch
    static action TotalApproved(limit as Integer, charges as List <of Integer>)
        returns Integer
        require limit >= 0 otherwise new ArgumentOutOfRangeException("limit")
        require charges is not nothing otherwise new ArgumentNullException("charges")
        let total = 0
        for_each charge in charges
            require charge > 0 otherwise new ArgumentOutOfRangeException("charge")
            if charge <= limit - total then
                total += charge
            end if
        end for_each
        return total
    end action
end class

The contracts expose invalid inputs. for_each processes the original order, and the conditional decides whether each valid charge fits. List and the exception types are ordinary .NET APIs. Naming a local Total rather than total would refer to the same local, not a second accumulator.

C++

// Approval policy: preserve authored row order and available-credit updates.
#include <cstdint>
#include <stdexcept>
#include <vector>

std::int32_t total_approved(std::int32_t limit,
                            const std::vector<std::int32_t>& charges)
{
    if (limit < 0) throw std::invalid_argument("limit");
    std::int32_t total = 0;
    for (const auto charge : charges) {
        if (charge <= 0) throw std::invalid_argument("charge");
        if (charge <= limit - total) total += charge;
    }
    return total;
}

The const reference avoids copying the vector and prevents this function from changing its elements. A valid C++ reference cannot be null; an empty vector is valid. The range check prevents overflowing the accumulator before making the approval decision.

Go

// Approval policy: preserve authored row order and available-credit updates.
package approval

import "errors"

func TotalApproved(limit int32, charges []int32) (int32, error) {
    if limit < 0 { return 0, errors.New("limit") }
    if charges == nil { return 0, errors.New("charges") }
    var total int32
    for _, charge := range charges {
        if charge <= 0 { return 0, errors.New("charge") }
        if charge <= limit-total { total += charge }
    }
    return total, nil
}

A caller checks the returned error before using the number. The zero returned alongside an error is not a successful total. This API explicitly distinguishes a nil slice from a present empty slice, which is a policy chosen to match the absent-input guard in the .NET version.

Java

// Approval policy: preserve authored row order and available-credit updates.
public final class ApprovalBatch {
    public static int totalApproved(int limit, int[] charges) {
        if (limit < 0) throw new IllegalArgumentException("limit");
        if (charges == null) throw new IllegalArgumentException("charges");
        int total = 0;
        for (int charge : charges) {
            if (charge <= 0) throw new IllegalArgumentException("charge");
            if (charge <= limit - total) total += charge;
        }
        return total;
    }
}

Java uses the same class-and-method shape and explicit argument guards. Its exception type differs from .NET, but this remains an argument failure rather than a successful partial result. An empty array produces zero.

Rust

pub fn total_approved(limit: i32, charges: &[i32]) -> Result<i32, &'static str> {
    if limit < 0 { return Err("limit"); }
    let mut total = 0;
    for &charge in charges {
        if charge <= 0 { return Err("charge"); }
        if charge <= limit - total { total += charge; }
    }
    Ok(total)
}

The borrowed slice is not mutated and cannot be null in safe Rust. Result makes failure part of the function's result type. The error path contains no approved total; callers can match it or propagate it from a compatible function.

JavaScript

// Approval policy: preserve authored row order and available-credit updates.
export function totalApproved(limit, charges) {
    if (!Number.isInteger(limit) || limit < -2147483648 || limit > 2147483647)
        throw new TypeError("limit must be a signed 32-bit integer");
    if (limit < 0) throw new RangeError("limit");
    if (!Array.isArray(charges)) throw new TypeError("charges must be an array");
    let total = 0;
    for (const charge of charges) {
        if (!Number.isInteger(charge) || charge < -2147483648 || charge > 2147483647)
            throw new TypeError("charge must be a signed 32-bit integer");
        if (charge <= 0) throw new RangeError("charge");
        if (charge <= limit - total) total += charge;
    }
    return total;
}

The numeric checks reject fractions, Booleans, strings, nonfinite values and out-of-range integers. JavaScript's number representation is exact for all values allowed here. Removing those guards would change the public input contract rather than merely shorten the example.

TypeScript

// Approval policy: preserve authored row order and available-credit updates.
export function totalApproved(limit: number, charges: readonly number[]): number {
    if (!Number.isInteger(limit) || limit < -2147483648 || limit > 2147483647)
        throw new TypeError("limit must be a signed 32-bit integer");
    if (limit < 0) throw new RangeError("limit");
    if (!Array.isArray(charges)) throw new TypeError("charges must be an array");
    let total = 0;
    for (const charge of charges) {
        if (!Number.isInteger(charge) || charge < -2147483648 || charge > 2147483647)
            throw new TypeError("charge must be a signed 32-bit integer");
        if (charge <= 0) throw new RangeError("charge");
        if (charge <= limit - total) total += charge;
    }
    return total;
}

The readonly input documents and checks the absence of mutation in typed callers. Runtime guards remain necessary for untyped callers and values whose number type admits fractions or infinities. Type erasure does not turn incoming JSON into a validated batch.

What to review

All seven versions validate before deciding whether to accept a charge. A late invalid item still fails the call, and an oversized item does not consume credit needed by later items. Case-insensitive program names, memory ownership, exception versus returned-error conventions and runtime numeric checks remain real differences between the languages.

The finite collection is present in the common workload. DirectSharp, Go, Java, JavaScript and TypeScript additionally reject their representable absent inputs; C++ references and safe Rust slices enforce that boundary through their types. None of these loops promises thread-safe coordination with another account update.

The comparison runner compiles and executes the exact fences with shared boundary assertions. For richer object and money examples, continue with customer validation in C#, VB.NET and Python, decimal pricing and async/resource lifetimes.

All comparisons · Delivery rules across languages