Execute Program

Everyday TypeScript: Unknown

Welcome to the Unknown lesson!

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

  • Sometimes we can't know a variable's type in advance. Here's one such situation:

  • Suppose that we're writing a library in TypeScript, but it will be published to NPM for public consumption. Some users will call our library from their JavaScript applications, where there's no type system present. When they call our exported functions, our types won't be enforced.

  • Maybe we have a function that takes a number argument. Hopefully, the user will pass a number to it, but they're not using TypeScript so they might pass a string. We'd like to tell TypeScript something like "we're not sure about this argument's true type, so please force us to check the value before moving on." We want TypeScript to force us to narrow the type using Array.isArray, or typeof x === 'number', or some other check.

  • As of TypeScript 3.0, we can do that with the unknown type. It means exactly what was described above: "we don't know what type this variable holds, so don't let us do anything with it until we find out."

  • We can assign any value to an unknown, so it's like any in that respect.

  • (These examples return undefined, because that's what a variable declaration statement evaluates to in JavaScript. The fact that they return undefined rather than giving a type error shows us that they compiled.)

  • >
    const n: unknown = 5;
    Result:
  • >
    const s: unknown = 'five';
    Result:
  • Unlike any, we can't use unknown in a place where TypeScript expects a string, a number, or (almost) any other type. Any attempt to do that is a type error. (You can answer these examples with type error.)

  • >
    const n: unknown = 5;
    const n2: number = n;
    n2;
    Result:
    type error: Type 'unknown' is not assignable to type 'number'.Pass Icon
  • >
    const s: unknown = 'five';
    const s2: string = s;
    Result:
    type error: Type 'unknown' is not assignable to type 'string'.Pass Icon
  • Those examples look a bit strange because we can see that n is a number and s is a string. But the compiler only cares about the types, and we explicitly told it that n and s have the type unknown.

  • What good is a variable if you can't read from it? To make unknown useful, we have to narrow the type back to a real, concrete type. For example, we can use conditional narrowing to narrow the unknown back to a string. Then we're back in the world of static types with a normal string, and all of the normal rules apply again.

  • However, narrowing will force us to handle the case where the unknown value isn't the type that we wanted. We can handle that in any way we like: throw a runtime error, use a default value, etc. In the examples below, we'll use a default value.

  • >
    const s: unknown = 'a string';
    const s2: string = typeof s === 'string' ? s : '';
    s2;
    Result:
    'a string'Pass Icon
  • >
    const s: unknown = 5;
    const s2: string = typeof s === 'string' ? s : '';
    s2;
    Result:
    ''Pass Icon
  • >
    const n: unknown = 5;
    const n2: number = typeof n === 'number' ? n : 0;
    n2;
    Result:
    5Pass Icon
  • >
    const n: unknown = 'a string';
    const n2: number = typeof n === 'number' ? n : 0;
    n2;
    Result:
    0Pass Icon
  • Let's compare any to unknown by writing an example for each.

  • Here's a code problem:

    Use any to make this code compile, even though it's wrong. (It's assigning a number value to a string.)

    const n: any = 5;
    const s: string = n;
    s;
    Goal:
    5
    Yours:
    5Pass Icon
  • Here's our "author's answer" from the code problem above, except that now we use unknown instead of any. Everything else is the same. Now we get a type error, showing our mistake.

  • >
    const n: unknown = 5;
    const s: string = n;
    s;
    Result:
    type error: Type 'unknown' is not assignable to type 'string'.Pass Icon
  • any and unknown are assignable to each other. This makes sense, because both mean "we don't know the type."

  • >
    const n: unknown = 5;
    const n2: any = n;
    n2;
    Result:
    5Pass Icon
  • >
    const n: unknown = 5;
    const n2: any = n;
    const n3: number = n2;
    n3;
    Result:
    5Pass Icon
  • Note that any is still unsafe! As soon as we have a variable of type any, we can assign it to anything. The next example makes an intentional type error that TypeScript can't detect because we used an any.

  • >
    const x1: unknown = 'five';
    const x2: any = x1;
    const x3: number = x2;
    x3;
    Result:
    'five'Pass Icon
  • We can also do the same thing in reverse. If we get an any from a JavaScript library, we can immediately put it into an unknown. That will stop us from accidentally assigning it directly to a concrete type that might be incorrect.

  • >
    const n: any = 'five';
    const n2: unknown = n;
    const n3: number = n2;
    Result:
    type error: Type 'unknown' is not assignable to type 'number'.Pass Icon
  • >
    const n: any = 'five';
    const n2: unknown = n;
    const n3: number = typeof n2 === 'number' ? n2 : 0;
    n3;
    Result:
    0Pass Icon
  • When we union unknown with another type, like type StringOrUnknown = unknown | string, the entire union is equivalent to just unknown. The unknown "absorbs" the string. Think of it this way: If the TypeScript compiler doesn't know the type of a variable, and we add in one more option, the compiler still doesn't know the type.

  • (unknown | any evaluates to any for the same reason. There's no practical reason to write that type, though.)

  • >
    const s: unknown | string = 'a string';
    const s2: string = s;
    s2;
    Result:
    type error: Type 'unknown' is not assignable to type 'string'.Pass Icon
  • When we intersect unknown with another type, like type UnknownIntersection = unknown & string, the other type absorbs the unknown. Think of it this way: If we don't know what type something is, but we also know that it's a string, then we actually know that it's just a string.

  • >
    const s: unknown & string = 'a string';
    const s2: string = s;
    s2;
    Result:
    'a string'Pass Icon
  • Finally, a note on terminology. any means "a variable of any type, assignable to anything". The name any is very appropriate; it applies to both reading and writing. unknown means "we don't know what's in this variable, so we can't use it until we find out".

  • For interoperability with existing JavaScript libraries, you'll sometimes find yourself using any. But in your own TypeScript code, we recommend using unknown to represent values whose type isn't known yet. Then the compiler will help you by forcing you to safely narrow them without losing the benefits of the type checker.