3 Topics
Chapter•65 Minutes

Basic Types, Inference, and Annotations

Learn primitive types, arrays, tuples, literals, inference, and how to annotate values without over-annotating.

Primitive Types

The everyday primitive types are string, number, boolean, bigint, symbol, and the special types null and undefined (with nullability behavior governed by compiler options). Use lowercase type names such as string, not the JavaScript wrapper object type String.

TypeScript also supports void for functions whose return value is not intended to be used, never for values that cannot occur, and object for non-primitive values. Avoid Object as a broad type because it does not express a useful data contract.

Primitive annotations
const username: string = "Mira";
const retryCount: number = 3;
const isEnabled: boolean = true;

function logMessage(message: string): void {
  console.log(message);
}
💡 The annotations express the intended values. In many cases the compiler can infer the types, so explicit annotations are most useful at public boundaries, function parameters, and places where inference would be unclear.
Key Takeaways
  • Use lowercase primitive type names.
  • void describes an intentionally unused return value.
  • Avoid broad wrapper types such as String and Object.
Knowledge Check

Which is the recommended TypeScript primitive type annotation?

Type Inference

Type inference lets the compiler derive a type from a value or expression. If you write const count = 3, TypeScript knows count is the literal type 3 in many contexts; a let variable is usually widened to number because it can be reassigned. Inference reduces noise while preserving checks.

Annotate when a type is not obvious, when you want a stable public contract, or when inference would be too narrow or too broad. Function parameter types generally need annotations unless contextual typing supplies them, such as in callbacks passed to typed APIs.

Inference and contextual typing
const appName = "Bytes4Dev";
let requestCount = 0;

const names = ["Asha", "Ravi"].map((name) => name.toUpperCase());
// name is inferred as string; names is string[]
💡 The compiler infers appName, requestCount, the array element type, and the callback parameter. Contextual typing supplies name: string from map's array signature.
Key Takeaways
  • Inference often makes explicit annotations unnecessary.
  • Use annotations to clarify contracts, not to repeat every inferred type.
  • Contextual typing provides types from the surrounding API.
Knowledge Check

What does TypeScript generally do when a callback is passed to a well-typed `map` call?

Arrays, Tuples, and Literal Types

Use T[] or Array<T> for arrays whose elements share a type. A tuple describes a fixed positional structure, such as [number, string]; optional and rest elements can model more flexible tuples. Literal types restrict values to specific strings, numbers, booleans, or unique symbols, and are often used for modes and discriminants.

as const preserves literal values and makes object properties and arrays readonly at the type level. It does not freeze the object at runtime. Prefer unions of literal values over a generic string when only a small known set is valid.

Array, tuple, and literal union
const scores: number[] = [82, 91, 76];
const userRecord: [id: number, name: string] = [7, "Mira"];

type Theme = "light" | "dark" | "system";
const theme: Theme = "dark";
💡 scores is an array of numbers, userRecord has two typed positions, and Theme permits only the three declared strings.
Key Takeaways
  • Arrays describe repeated values; tuples describe positions.
  • Literal unions constrain a value to known alternatives.
  • as const affects compile-time inference, not runtime immutability.
Knowledge Check

Which type best models a value that can only be `"small"`, `"medium"`, or `"large"`?