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

  1. Fix the comparator to be a consistent total order: return -1/0/1 explicitly, never rely on integer subtraction that can overflow.
  2. Make comparator results stable: do not sort objects whose comparison key mutates mid-sort.
  3. Ensure Compare(a,b) == -Compare(b,a) and transitivity holds; add a unit test that checks these properties on representative data.
  4. 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

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)