Grokking TypeScript
Vote

0% completed

​

Variables and Data Types

Variables

Data Types

Primitive Types

Complex Types

Allowing null and undefined

any and unknown

Type Assertions

Type Annotations

Type Inference

Key Takeaways

An order in an online store holds an order code, a quantity, a customer, a list of prices, and a tracking code that may not exist yet. Describing those values correctly takes variables and the basic data types of TypeScript, together with annotations and inference.

Variables

TypeScript declares variables with let, const, or var, just as JavaScript does, and it adds optional type annotations for safety and predictability:

  • let declares a variable that can be reassigned. Use it for values that change over time, such as counters or state in UI frameworks.
  • const declares a variable that cannot be reassigned. Use it for configuration values and for anything else that should not change.
  • var is legacy syntax. It is similar to let, but its scope is the whole function instead of the block.
let quantity: number = 5; // A variable with type number const orderCode: string = "A-100"; // A constant with type string let customerName = "Alice"; // Inferred as string

Use const unless the variable needs to be reassigned, because the compiler then rejects any accidental reassignment with error TS2588: Cannot assign to 'orderCode' because it is a constant. Note that const protects only the variable, not the object inside it. After const order = { quantity: 3 }, the line order.quantity = 4 is still allowed.

Avoid the variable names name and status at the top level of a file. The compiler includes the type definitions of the browser by default, and they already declare those two names as global variables. A file with no import or export shares that global scope, so let name = "John"; fails with error TS2451: Cannot redeclare block-scoped variable 'name'. This lesson uses other names for that reason.

Data Types

A data type defines what kind of value a variable can hold.

Primitive Types

JavaScript has seven primitive types, and TypeScript uses the same seven:

  • string represents text.
  • number represents both integers and floating-point numbers.
  • boolean represents true or false.
  • null and undefined indicate the absence of a value.
  • symbol represents a unique and immutable value.
  • bigint represents integers too large for number to hold exactly. For example, 9007199254740993n keeps its value, but the number 9007199254740993 prints as 9007199254740992.

Complex Types

Three more types describe values that hold other values:

  • Arrays hold a collection of elements of one type.
  • Tuples are arrays with a fixed number of elements, where each position has its own type.
  • Objects hold key-value pairs.
const prices: number[] = [20, 5]; const buyer: { name: string; age: number } = { name: "Bob", age: 30 }; const coordinates: [number, number] = [10, 20];

Use these types to enforce structure, especially when you handle structured data from an API. The tuple rule has one exception. The compiler rejects a third element in the initial value and rejects a read of coordinates[2], but it allows coordinates.push(30), which really does make the array longer at runtime.

Allowing null and undefined

In strict mode, null and undefined are not part of any other type, so a string variable cannot hold them:

let trackingCode: string = null;
tracking.ts(1,5): error TS2322: Type 'null' is not assignable to type 'string'.

To allow them, say so in the type with | null or | undefined. The | symbol means "or":

let trackingCode: string | null = null; // A string or null

any and unknown

Two types accept every value, and they treat it in opposite ways. With any, the compiler stops checking the value, so a mistake appears only at runtime. With unknown, the compiler refuses every use until you prove what the value is:

const anyValue: any = 42; anyValue.toUpperCase(); // Compiles, then throws a TypeError at runtime const unknownValue: unknown = "hello"; unknownValue.toUpperCase(); // Error if (typeof unknownValue === "string") { console.log(unknownValue.toUpperCase()); // Allowed: the check proves it is a string }
values.ts(5,1): error TS18046: 'unknownValue' is of type 'unknown'.

Prefer unknown when you do not know the type of a value, because it keeps the checks that any removes.

Type Assertions

Sometimes you know more about a value than the compiler can infer. A type assertion, written with as, tells the compiler to treat a value as a specific type:

const someValue: unknown = "Hello, World!"; const stringLength: number = (someValue as string).length;

An assertion is only an instruction to the compiler. It does not convert the value, and it adds no check at runtime. If someValue held the number 42, someValue as string would still compile, and the value would still be a number when the program ran. The chapter on advanced types covers assertions in more detail.

Type Annotations

You met annotations in the previous lesson. They are explicit types on variables, function parameters, and return values, and they let the compiler catch errors during development:

let customerEmail: string = "alice@example.com"; // Variable annotation function greet(customer: string): string { // Parameter and return annotations return `Hello, ${customer}`; }

Annotate the return type when a function is part of what other code depends on. The compiler then checks the function body against your stated intention, instead of inferring whatever the body happens to return. This matters most in libraries and frameworks, whose users need to see what types of values are expected.

Type Inference

Type inference works at every project size, and it saves you from annotating each variable. The compiler infers variable types from assignments, and it also infers the return type of a function from its return statements:

let confirmation = "Thank you!"; // Inferred as string function multiply(a: number, b: number) { return a * b; // The return type is inferred as number }

The return type of multiply is checked at each call, even though nobody wrote it. For a small internal function, this inference is usually enough.

Key Takeaways

  • const prevents reassignment, but it does not freeze an object.
  • null and undefined must be allowed explicitly in strict mode.
  • Prefer unknown to any, and remember that as converts nothing.
  • Annotate the return type of a function that other code depends on, and let the compiler infer the types of small internal functions.

Reading Progress

0%


Vote for new content

On This Page

Variables

Data Types

Primitive Types

Complex Types

Allowing null and undefined

any and unknown

Type Assertions

Type Annotations

Type Inference

Key Takeaways