Skip to content
aviral gupta

// B2.1 · ~30 min · Beginner

Primitives, arrays, any and unknown

After this lesson you can annotate text, numbers, true/false values and arrays, and handle a value of unknown type with unknown and typeof instead of any.

Lesson 1 of 5 in B2 Everyday types

Start of the module

You will be able to

  • Annotate values with string, number and boolean, and arrays with number[] or Array<number>
  • Explain what any does to type checking, and why strict (noImplicitAny) reports an implicit any
  • Use unknown for a value whose type you do not know yet, and check it with typeof before using it
  1. Warm-up · Activity 1 of 7

    Warm-up from JavaScript: which of these can typeof give for a value? Pick all that apply.

    Select all that apply.

  2. Predict · Activity 2 of 7

    Predict before you read on: data is annotated as any. What does npx tsc report?

    let data: any = "hello";
    data = 42;
    data.toUpperCase();
    const total: number = data;
  3. Practice · Activity 3 of 7

    Fill in the type: an array of numbers.

    const temperatures: ____ = [18.5, 19.25, 21];
    const temperatures: = [18.5, 19.25, 21];
  4. Practice · Activity 4 of 7

    value is unknown this time. What does tsc report?

    function shout(value: unknown) {
      return value.toUpperCase();
    }
  5. Practice · Activity 5 of 7

    Match each type to what it allows.

  6. Brain teaser · Activity 6 of 7

    Brain teaser. tsc reports no errors for this file. What does node main.ts print?

    const settings = JSON.parse('{"volume": 7}');
    console.log(settings.volum.toFixed(1));
  7. Apply · Activity 7 of 7

    Mini-task. Describe an album you like with four annotated constants: a title (string), a year (number), whether you own it (boolean) and its track names (string[]). Then write describe(value: unknown): string, which returns "text: " plus the value when typeof says it is a string, and "not text" otherwise. Print the constants and call describe on a track name and on the year. Run npx tsc, then node main.ts. Finally, change unknown to any, remove the typeof check, and see that tsc no longer complains about value.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A weather station with one unknown input

The station's name, status, readings and labels are annotated with the everyday types. formatReading takes unknown, because a sensor can send anything: it uses value as a number only after typeof has confirmed it is one. Run npx tsc, which prints nothing, then node main.ts. Then delete the if line and its closing brace and run tsc again: it reports error TS18046 at value.toFixed.

main.ts

// A weather station: annotated values, and one input whose type is unknown.
const station: string = "Berlin-Mitte";
const online: boolean = true;
const readings: number[] = [18.5, 19.25, 21];
const labels: Array<string> = ["morning", "noon", "evening"];

function formatReading(value: unknown): string {
  if (typeof value === "number") {
    return value.toFixed(1) + " degrees";
  }
  return "no reading";
}

console.log(station, "online:", online);
for (let i = 0; i < readings.length; i++) {
  console.log(labels[i] + ":", formatReading(readings[i]));
}
console.log("sensor 4:", formatReading("offline"));

Run it with

npx tsc
node main.ts

Output

Berlin-Mitte online: true
morning: 18.5 degrees
noon: 19.3 degrees
evening: 21.0 degrees
sensor 4: no reading
  • number[] and Array<string> are the two ways to write an array type; both check every element.
  • readings[i] is a number and labels[i] a string, because an element has the array's element type.
  • formatReading("offline") type-checks, since unknown accepts anything; the typeof check sends it to "no reading".
  • toFixed(1) rounds 19.25 to 19.3 and writes 21 as 21.0.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.

Exercises

Exercise 1 of 2

Fix the playlist

The annotations are right, but tsc reports one wrong element in each array. The third track is called "7", is 2.75 minutes long, and you like it. Fix the three values so that they match their array's type. Leave the annotations and the function as they are. The first test is the type-check.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    Run npx tsc: each diagnostic names the line and column of one wrong element.

  2. Hint 2

    A title is text even when it looks like a number, so it needs quotes. A length is a number, so it must not have them.

  3. Hint 3

    The fixed values are "7", 2.75 and true.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export const titles: string[] = ["Intro", "Blue", "7"];
export const lengths: number[] = [3.5, 4, 2.75];
export const liked: boolean[] = [true, false, true];

export function totalMinutes(values: number[]): number {
  let sum = 0;
  for (const value of values) {
    sum += value;
  }
  return sum;
}
Run it on your computer

Install TypeScript 7.0 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.ts

export const titles: string[] = ["Intro", "Blue", 7];
export const lengths: number[] = [3.5, 4, "2.75"];
export const liked: boolean[] = [true, false, "true"];

export function totalMinutes(values: number[]): number {
  let sum = 0;
  for (const value of values) {
    sum += value;
  }
  return sum;
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {titles, lengths, liked, totalMinutes} from './main.ts';

test('The third title is the text "7"', () => {
  assert.deepEqual(titles, ['Intro', 'Blue', '7'], 'titles should be ["Intro", "Blue", "7"], all text');
});

test('The playlist lasts 10.25 minutes', () => {
  assert.equal(totalMinutes(lengths), 10.25, `totalMinutes(lengths) should be 10.25, got ${JSON.stringify(totalMinutes(lengths))}`);
});

test('liked holds only true and false', () => {
  assert.deepEqual(liked, [true, false, true], 'liked should be [true, false, true], without text');
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc needs TypeScript in the folder: run npm install --save-dev typescript there once. tsc checks the types; Node.js runs main.ts by removing them.

Run the program:

npx tsc
node main.ts

Run the checks (needs learnrun.js in the same folder):

npx tsc
node --test
Download learnrun.js

Exercise 2 of 2

From any to unknown

describe takes any, so tsc accepts value.length even when value is a number, and describe(42) returns Text of length undefined. Change any to unknown and read what tsc reports. Then add a typeof check, so that text gives "Text of length N" and every other value gives "Not text".

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the TypeScript compiler (up to 2.1 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    With unknown, tsc reports error TS18046: 'value' is of type 'unknown'. That is the point: now it wants a check.

  2. Hint 2

    typeof value === "string" is true only for text. Inside that if block, value is a string.

  3. Hint 3

    Return "Text of length " + value.length inside the if, and "Not text" after it.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export function describe(value: unknown): string {
  if (typeof value === "string") {
    return "Text of length " + value.length;
  }
  return "Not text";
}
Run it on your computer

Install TypeScript 7.0 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.ts

export function describe(value: any): string {
  return "Text of length " + value.length;
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {describe} from './main.ts';

test('Text gives its length', () => {
  assert.equal(describe('hello'), 'Text of length 5', 'describe("hello") should be "Text of length 5"');
});

test('A number is not text', () => {
  assert.equal(describe(42), 'Not text', `describe(42) should be "Not text", got ${JSON.stringify(describe(42))}`);
});

test('An array is not text either', () => {
  assert.equal(describe(['a', 'b']), 'Not text', 'describe(["a", "b"]) should be "Not text"');
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc needs TypeScript in the folder: run npm install --save-dev typescript there once. tsc checks the types; Node.js runs main.ts by removing them.

Run the program:

npx tsc
node main.ts

Run the checks (needs learnrun.js in the same folder):

npx tsc
node --test
Download learnrun.js

Common mistakes

String with a capital S

const city: String = "Berlin";
const upper: string = city;
console.log(upper);

What tsc or Node.js prints

main.ts(2,7): error TS2322: Type 'String' is not assignable to type 'string'.

Why, and the fix

String with a capital S is the type of JavaScript's String wrapper object, not of text. tsc adds a second line: 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. The same goes for Number and Boolean. Write the types in lower case: const city: string = "Berlin".

One number where an array was meant

const ids: number = [1, 2, 3];
console.log(ids);

What tsc or Node.js prints

main.ts(1,7): error TS2322: Type 'number[]' is not assignable to type 'number'.

Why, and the fix

number is a single number; an array of numbers is number[]. The message says what was found (number[]) and what the annotation expects (number). Add the brackets: const ids: number[] = [1, 2, 3]. Or leave the annotation out: TypeScript infers number[] from the value.

Using an unknown value without a check

function shout(value: unknown): string {
  return value.toUpperCase();
}

console.log(shout("hi"));

What tsc or Node.js prints

main.ts(2,10): error TS18046: 'value' is of type 'unknown'.

Why, and the fix

unknown means "any value, and I have not checked which yet", so tsc allows no method, property or arithmetic on it. Check first: if (typeof value === "string") { return value.toUpperCase(); }, then return something else for other values. Do not switch to any to silence the error: that removes the check the error was asking for.

TypeScript in the browser: the TypeScript 6.0.3 compiler, Apache-2.0, then your browser’s own engine. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

Three primitives, in lower case

Most values you annotate are text, numbers or true/false. Their types are string, number and boolean, the same names JavaScript's typeof gives. number covers whole and decimal numbers alike: there is no int or float, and int is an error (Cannot find name 'int'). Write the names in lower case. String, Number and Boolean with a capital letter are wrapper objects that almost never belong in your code: const upper: string = city, where city is a String, fails with Type 'String' is not assignable to type 'string'. The course tsconfig also knows bigint, null and undefined, which later lessons use.

Arrays: number[] or Array<number>

An array type is the element type followed by brackets: number[] is an array of numbers, string[] an array of text. Array<number> means exactly the same, and tsc even prints it as number[]. The annotation checks every element: const flags: boolean[] = [true, "no"] is reported at the "no", and scores.push("7") on a number[] is reported at the argument. Every element you read back has the element type, so readings[0].toFixed(1) is allowed. Mind the brackets: number is one number, so const ids: number = [1, 2, 3] fails, and [number], with the brackets outside, is a different type (a tuple) for exactly one number.

any checks nothing; unknown checks everything

any switches type checking off for a value. You can call any method on it and assign it anywhere, and tsc stays silent: data.toUpperCase() on a number type-checks, then fails when it runs. JSON.parse returns any, so a typo in what you read from it is never reported. When TypeScript cannot work out a type, as for a parameter without an annotation, it would fall back to any; strict includes noImplicitAny, which turns that into error TS7006. unknown also accepts every value, but allows nothing until you check what it is: value.length gives error TS18046: 'value' is of type 'unknown'. Inside if (typeof value === "string") { … }, value is a string, and you can use it as one.

Sources

Last reviewed September 30, 2026