I'm familiar with JS, however just today started TypeScript. Experimenting with the type system and generics, I thought to implement a comparator yielding 3 distinct results and created the following:

export function compare<T>(s1: T, s2: T): number {
    let equal = 0;
    let greater = 1;
    let lesser = -1

    // Special cases: consider "undefined" and "null" to be less than defined
    if (nullOrUndefined(s1) && nullOrUndefined(s2)) {
        return equal;
    } else if (nullOrUndefined(s1) ^ nullOrUndefined(s2)) {
        return nullOrUndefined(s2) ? greater : lesser;

    // Ignore case for strings
    if (typeof s1 === 'string') {
        s1 = s1.toLowerCase();
        s2 = s2.toLowerCase();

    // Use JS default
    return s1 === s2 ? equal : s1 > s2 ? greater : lesser;

export function nullOrUndefined(input: any): boolean {
    return input === undefined || input === null;

My questions are:

  1. Are the variables necessary? I keep going back and forth on this / wonder if it aids clarity in any way. Sometimes feel like it's intuitive, but I even wonder if an enumeration representing comparison results outside could be superior.
  2. My first implementation used the any type, but it feels more appropriate to apply it this way since it should always compare like-items. This feels like a sensical assumption, but would any prove more handy and 'generic' in any case?
  3. For any unfamiliar with JS weirdness, see this article.
  • \$\begingroup\$ return input === undefined || input === null is not TS idiomatic, I believe. return input == null would achieve same result. \$\endgroup\$ Oct 6 '17 at 5:45
  • \$\begingroup\$ I hesitate to write a real answer, because I'm not sure what is the contract of this function. How exactly should it work on array types (T[]), complex object types, and so on. Another thing is the assumption of always compare like-items is only appropriate under some special limitations, namely that there will be no TS class hierarchies involved into the equation. Take a look at this scenario. I think your question is specific, but it should be either improved or closed since code does not do what it claims to do. \$\endgroup\$ Oct 6 '17 at 5:57

Personally, because this function can be simplified, I don't believe the variables are necessary. Since you are using Typescript, an enum would fit well here, but I believe even this is unnecessary. The results of a compare function are well standardized.

enum Result {
  equal = 0,
  greater = 1,
  lesser = -1

You are correct that it is more appropriate to apply the function with generics. If somewhere you need the flexibility that any provides, you can simply call the function with compare<any>(a, b) to override the type. I personally have found that any is only useful when first converting a project and will avoid it like the plague if at all possible.

Now, a few comments about the function itself.

  1. I'm not sure how you are compiling this, but it emits three errors in the playground.

    1. nullOrUndefined(s1) ^ nullOrUndefined(s2) - The '^' operator is not allowed for boolean types. Consider using '!==' instead.
    2. s1 = s1.toLowerCase();- Type 'string' is not assignable to type 'T'.
    3. s2 = s2.toLowerCase(); - Property 'toLowerCase' does not exist on type 'T'.
  2. You will have problems if you try to use this function with the built in Array.prototype.sort function because undefined results in the array being treated as a sparse array. See the spec.

  3. To ignore string case, it would be better to use String.prototype.localeCompare.

Here is how I would implement this function. I purposely use == instead of === for comparing null as it also catches undefined.

function compare<T>(a: T, b: T): number {
    if (a == b) return 0;
    if (a == null) return -1;
    if (b == null) return 1;

    // To appease the type system, check both
    // I think it might be a bug that this is required
    if (typeof a === 'string' && typeof b === 'string') {
        return a.localeCompare(b, 'standard', { sensitivity: 'case'});

    return b > a ? -1 : 1;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.