Alright, let’s get down to brass tacks. You’re writing JavaScript, but you want to do it properly. You’re tired of undefined is not a function and you’ve decided to enlist TypeScript’s help. Good choice. The first and most fundamental step is telling TypeScript what kind of stuff you’re putting in your variables. This isn’t bureaucracy; it’s a force field against your own future, dumb mistakes.

We start with the primitives: the basic building blocks of data. You already know these from JavaScript, but now we’re going to be explicit about them.

The Core Trio: string, number, boolean

These are the workhorses. Annotating them is straightforward: you just use a colon followed by the type after the variable name.

let username: string = "alice";
let age: number = 30;
let isLoggedIn: boolean = false;

Seems trivial, right? The magic isn’t in the annotation itself, but in the protection it affords. Try this:

username = 42; // Error: Type 'number' is not assignable to type 'string'.
isLoggedIn = "yes"; // Error: Type 'string' is not assignable to type 'boolean'.

This is TypeScript’s core value proposition: it catches these type mismatches at compile time, saving you from a runtime error. Notice I said “compile time.” TypeScript is a development tool. It checks your types and then erases them, leaving behind pristine, runnable JavaScript. You’re not shipping type annotations to the browser.

Why not just use let isLoggedIn = false; and let TypeScript infer the type? You absolutely can, and for trivial cases like that, you should. Inference is your friend. But the moment the value might be ambiguous or come from a dynamic source (like user input or an API response), you should slap an annotation on it. It makes your intent crystal clear, both to the compiler and to the next poor soul (probably you, in six months) who has to read your code.

Beyond the Basics: bigint and symbol

These two are the esoteric cousins who show up to the family reunion once a decade. You might not use them daily, but when you need them, you really need them.

bigint is for when number isn’t big enough. JavaScript numbers are double-precision floating point, which means they have a maximum safe integer value (Number.MAX_SAFE_INTEGER, which is 2^53 - 1). If you’re dealing with numbers larger than that (hello, finance and 64-bit identifiers), you need bigint. You create one by appending n to the literal.

let massiveId: bigint = 9007199254740993n; // This would be unreliable as a `number`
let anotherBiggie: bigint = BigInt("9007199254740993"); // You can also use the constructor

symbol is a primitive guaranteed to be unique. Its main use case is for creating unique property keys on objects to avoid name collisions, which is a fancy way of saying “when you’re doing advanced meta-programming and don’t want anyone accidentally overriding your stuff.”

const SECRET_KEY: unique symbol = Symbol("super-secret-internal-key");
let obj = {
  [SECRET_KEY]: "shh, don't tell anyone"
};
// This key is guaranteed to be unique and can't be stumbled upon by accident.

The Perils of Implicit any

Here’s the first real pitfall. What happens if you declare a variable but don’t immediately give it a value? TypeScript’s default behavior is to say, “Well, I have no idea what you’re going to put here,” and give it the type any. This is a trap. any is the nuclear option that disables all type checking. It’s essentially a rollback to plain JavaScript.

let dangerousVariable; // TypeScript implicitly types this as `any`
dangerousVariable = "a string";
dangerousVariable = 42; // This is fine... for now. And that's the problem.

To avoid this, you have two better options:

  1. Use a type annotation even if you don’t assign a value yet.
  2. Enable the noImplicitAny compiler flag (which you absolutely should), which will make the above example an error.
let safeString: string;
safeString = "Hello"; // Good.
safeString = 42; // Error. Crisis averted.

let safeButUndefined: number | undefined; // More on union types later, but this is explicit.

Literal Types: Being Extremely Specific

Sometimes, you don’t just want a string, you want one specific string. TypeScript lets you do this, and it’s more useful than it sounds.

let direction: "left" | "right" | "up" | "down"; // This is a union of string literal types.
direction = "left"; // Perfectly valid.
direction = "diagonal"; // Error: Type '"diagonal"' is not assignable to type...

let answer: 42;
answer = 42; // Okay.
answer = 43; // Error: Type '43' is not assignable to type '42'.

This is your first taste of TypeScript’s power beyond simple types. You’re not just describing kind, you’re describing value, which is incredibly powerful for defining precise APIs and state shapes. It’s the compiler being your brilliant, pedantic friend who remembers every single detail you agreed on.