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.