Execute Program

Everyday TypeScript: Arrays Are Objects

Welcome to the Arrays Are Objects lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • In most languages, arrays are separate from what JavaScript calls "objects". But JavaScript entangles the two in a confusing way.

  • When we ask JavaScript what typeof [1, 2, 3] is, we might expect to get 'array'. However, due to design decisions made back in the 1990s, the type of an array is 'object'.

  • >
    typeof [1, 2, 3];
    Result:
    'object'Pass Icon
  • This has many implications, some of which can be confusing. For example, we can build an array with some values inside of it, but we can also set object properties on that same array! When we set a property on an array, it doesn't affect the array's contents.

  • Here's an example showing that in action. In this example, we've used the any type. Otherwise, TypeScript wouldn't let us set properties on the array.

  • >
    const array: any = [1, 2, 3];
    array.someProperty = 'some value';
    array;
    Result:
    [1, 2, 3]Pass Icon
  • Although the property doesn't show up in any array index, we can still access it with the regular .someProperty syntax.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    array.someProperty;
    Result:
    'some value'Pass Icon
  • This design decision in JavaScript has caused cascading complexity over multiple decades, and it continues to affect us in TypeScript. For example, we might want to take an unknown argument, use typeof to narrow it to an array, then return its length. We can write that function, but we can't use if (typeof(maybeArray) === 'array') { ... }. Instead, we have to use the built-in Array.isArray function.

  • >
    function lengthOrUndefined(maybeArray: unknown) {
    if (Array.isArray(maybeArray)) {
    return maybeArray.length;
    } else {
    return undefined;
    }
    }

    [
    lengthOrUndefined([1, 2]),
    lengthOrUndefined('not an array'),
    ];
    Result:
    [2, undefined]Pass Icon
  • Fortunately, Array.isArray solves that problem cleanly. But the blurred line between objects and arrays shows up in other, more subtle ways.

  • In an earlier lesson on index signatures, we saw that they can describe objects or arrays:

  • >
    type LoginCounts = {[userName: string]: number};
    const loginCounts: LoginCounts = {
    Amir: 5,
    Betty: 7,
    };
    loginCounts.Betty;
    Result:
    7Pass Icon
  • >
    const strings: {[index: number]: string} = ['a', 'b', 'c'];
    strings[0];
    Result:
    'a'Pass Icon
  • It's true that index signatures can describe objects or arrays, but it's also a bit deceptive to say it in that way. It's more accurate to say that index signatures only describe objects. Arrays are just a special kind of object where the property keys happen to be numbers.

  • Here's one final way to see the relationship between arrays and objects. In an earlier lesson, we saw the object type. It's almost never useful in normal code, so we recommended avoiding it. But in this case, we can use it to show the relationship between objects and arrays. If a function takes an object, TypeScript will happily let us pass an array.

  • >
    function takesAnObject(o: object) {
    return 'it worked';
    }

    [
    takesAnObject({name: 'Amir'}),
    // This is an array, but arrays are objects so it's OK!
    takesAnObject([1, 2, 3]),
    ];
    Result:
    ['it worked', 'it worked']Pass Icon
  • For most TypeScript code, you won't have to think about the fact that arrays are technically objects. You'll deal with straightforward types like Array<number> and {name: string}.

  • But sometimes the line will blur, most commonly in type guards, index signatures, and some situations involving generics. If you ever find yourself asking "why is TypeScript talking about my object having number properties?" then it's probably time to step back and ask "is this because arrays are actually objects?"