TheAlgorithms/C-Sharp · error
Comparison method violates its general contract!
Error message
Comparison method violates its general contract!
What it means
TimSorter's FinalizeMerge throws ArgumentException("Comparison method violates its general contract!") when a merge finishes with left.Remaining == 0, meaning the comparator produced an inconsistent ordering — elements were neither strictly ordered nor correctly interleaved. This is the classic TimSort invariant break caused by a comparator that violates transitivity/antisymmetry (e.g. returning 0 for non-equal items or inconsistent results across calls).
Solutions
- Fix the comparator to be a consistent total order: return -1/0/1 explicitly, never rely on integer subtraction that can overflow.
- Make comparator results stable: do not sort objects whose comparison key mutates mid-sort.
- Ensure Compare(a,b) == -Compare(b,a) and transitivity holds; add a unit test that checks these properties on representative data.
- If a subset triggers it, isolate the data and log the offending pairs to find the inconsistent comparison.
Example fix
// before list.Sort((a, b) => a.Score - b.Score); // after list.Sort((a, b) => a.Score.CompareTo(b.Score));
Defensive patterns
Strategy: validation
Validate before calling
// Verify comparator consistency before sorting:
static bool IsConsistent<T>(Comparison<T> cmp, IEnumerable<T> sample) =>
sample.All(a => sample.All(b =>
Math.Sign(cmp(a, b)) == -Math.Sign(cmp(b, a)))); Try / catch
try { sorter.Sort(data); }
catch (ArgumentException ex) when (ex.Message.Contains("general contract")) { /* fall back to a safe comparator, e.g. Comparer<T>.Default */ } Prevention
- Use CompareTo instead of subtraction in comparators to avoid overflow.
- Never mutate sort keys while a sort is in progress.
- Property-test comparators for transitivity and antisymmetry.
- Return only -1/0/1 (via Math.Sign) from custom comparisons.
When it happens
Trigger: Sorting with a comparison delegate that is not a valid total order: Compare(a,b) inconsistent with Compare(b,a), non-transitive results, comparator returning inconsistent values for the same pair across calls (mutable keys), or subtracting ints and overflowing instead of returning -1/0/1.
Common situations: Comparators written as (a,b) => a.Value - b.Value that overflow; sorting objects whose key changes during the sort (multi-threaded mutation); comparators using floating-point or null handling inconsistently; code updated to a new TimSort-style sort after working with an older, less strict sort algorithm.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
AI-assisted analysis of TheAlgorithms/C-Sharp@96e2905cab (2026-09-13).
Data as JSON: /api/errors/022e55644d5798f0.
Report an issue: GitHub.
Appendix: source
Thrown at Algorithms/Sorters/Comparison/TimSorter.cs:211
return true;
}
/// <summary>
/// Moves over the last parts of the chunks.
/// </summary>
/// <param name="left">TimChunk of the left hand side.</param>
/// <param name="right">TimChunk of the right hand side.</param>
/// <param name="dest">The current target point for the remaining values.</param>
private static void FinalizeMerge(TimChunk<T> left, TimChunk<T> right, int dest)
{
if (left.Remaining == 1)
{
Array.Copy(right.Array, right.Index, right.Array, dest, right.Remaining);
right.Array[dest + right.Remaining] = left.Array[left.Index];
}
else if (left.Remaining == 0)
{
throw new ArgumentException("Comparison method violates its general contract!");
}
else
{
Array.Copy(left.Array, left.Index, right.Array, dest, left.Remaining);
}
}
/// <summary>
/// Returns the length of the run beginning at the specified position in
/// the specified array and reverses the run if it is descending (ensuring
/// that the run will always be ascending when the method returns).
///
/// A run is the longest ascending sequence with:
///
/// <![CDATA[a[lo] <= a[lo + 1] <= a[lo + 2] <= ...]]>
///
/// or the longest descending sequence with:
///View on GitHub (pinned to 96e2905cab)