Right, let’s get our hands dirty. We’ve set up the machinery; now let’s see what it actually does. The core premise of TypeScript is simple: it’s JavaScript with a type system slapped on top. But don’t let that simplicity fool you—this is where the magic, and more importantly, the sanity, happens.

You already know how to write this in JavaScript:

// plain-old-javascript.js
function greet(name) {
    return "Hello, " + name;
}

console.log(greet("Alice")); // "Hello, Alice"
console.log(greet(42));      // "Hello, 42" ... wait, what?

The JavaScript version is perfectly happy, if a bit naive. It will cheerfully concatenate a number onto a string, which might not be what you intended. It’s like a friendly golden retriever that brings you a shoe instead of a ball—the enthusiasm is there, but the execution is flawed.

Now, let’s write the same thing in TypeScript. The key difference is that we’re going to tell the compiler what we expect name to be.

// first-typescript.ts
function greet(name: string) {
    return "Hello, " + name;
}

console.log(greet("Alice")); // "Hello, Alice"
console.log(greet(42));      // 💥 Compiler Error!

See that : string? That’s a type annotation. It’s your directive to the TypeScript compiler. You’re saying, “Hey, just so we’re clear, the name parameter for this function is always a string.” It’s not a suggestion; it’s a rule you’re setting for your code.

The Compiler is Your New Best Friend (and Nitpicky Critic)

Run tsc first-typescript.ts now. Instead of silently creating a .js file, the TypeScript compiler will immediately slap your wrist:

error: Argument of type 'number' is not assignable to parameter of type 'string'.

This is the core value proposition. You caught a potential bug before you even ran the code. You’re moving errors from runtime (where your users find them) to compile time (where you, the developer, can fix them in the safety of your editor). This is a monumental shift. It’s the difference between checking the weather forecast before a hike and realizing you’re in a thunderstorm only after you’re soaked.

What Actually Gets Generated?

Let’s fix our code by removing the offending line and compiling it properly.

// fixed-greet.ts
function greet(name: string) {
    return "Hello, " + name;
}

console.log(greet("Alice"));

Run tsc fixed-greet.ts. Now look at the generated fixed-greet.js:

// fixed-greet.js
function greet(name) {
    return "Hello, " + name;
}
console.log(greet("Alice"));

Wait a minute. Where did the type annotation go? It’s completely stripped out. This is a crucial point: TypeScript types exist only at compile time. They are not part of the final JavaScript that runs in the browser or Node.js. The compiler uses them to validate your logic and then discards them, producing clean, standards-compliant JavaScript. This means you can adopt TypeScript incrementally without worrying about bloating your production code or breaking compatibility.

Embracing the “Strict” Life

Out of the box, the TypeScript compiler is… lenient. It has a whole suite of options you can enable to make it much stricter and, consequently, much more helpful. This is controlled in your tsconfig.json file. The most important one is strict, which is a master switch for a whole group of sensible strictness checks.

While we’ll dive into tsconfig.json later, you should know that most new, serious projects set "strict": true from the get-go. It feels painful at first—the compiler will complain about things you didn’t even know were problems—but it forces you to write far more robust code. It’s like having a brilliant but brutally honest coach.

The Pitfall of Assuming “Any” is Okay

If you don’t provide a type annotation and the compiler can’t figure it out (a process called type inference), it will default to a type called any. This is the compiler throwing its hands in the air and saying, “Fine, do whatever you want. I give up.” It’s an escape hatch, and you should treat it like a fire alarm—only to be used in emergencies.

function greet(name) {  // The type of 'name' is implicitly 'any'
    return "Hello, " + name;
}

console.log(greet("Alice")); // OK
console.log(greet(42));      // Also OK... and that's the problem!

This code compiles without a peep, completely defeating the purpose of using TypeScript. Your goal is to gradually eliminate any from your codebase. We’ll cover how to configure the compiler to warn you about this lazy behavior later. For now, just remember: be explicit. Tell the compiler what you mean. It’s not a mind reader (yet), but it’s an incredibly fast and diligent code reviewer.