Foundations Updated 2026-09 View as Markdown

Variables and Types

TypeScript is JavaScript plus a type checker. This is where the types start — and where you stop writing var.

TypeScript is JavaScript with a type checker on top. Every valid JavaScript file is valid TypeScript — the difference is that TypeScript reads your code before it runs and tells you when something cannot possibly work.

Declaring a variable#

typescript
let count = 0;          // can be reassigned
const name = "Ada";     // cannot be reassigned

Use const by default and let when you genuinely need to reassign. Never use var — it is function-scoped rather than block-scoped, which produces surprising behaviour, and there is no situation in modern code where it is the right choice.

typescript
if (true) {
  var leaks = "visible outside this block";
  let contained = "not visible outside this block";
}
console.log(leaks);      // works — this is the problem
console.log(contained);  // error: Cannot find name 'contained'

Type annotations#

You can tell TypeScript what a variable holds with : type after the name.

typescript
let age: number = 36;
let username: string = "ada";
let isActive: boolean = true;

Now the checker holds you to it:

typescript
age = "thirty-six";
// Type 'string' is not assignable to type 'number'.

That error appears in your editor as you type — before you run anything, before a test fails, before it reaches production. That is the whole value proposition.

Inference: usually you do not need the annotation#

TypeScript works out the type from the value, so the annotation above is redundant:

typescript
let age = 36;            // inferred as number
age = "thirty-six";      // still an error

Prefer inference. Write the annotation when it adds information the value does not carry — a function's parameters and return type, an empty array, or a variable you declare before assigning.

typescript
const items: string[] = [];      // without this it would be any[]
let selected: string | null = null;

The primitive types#

typescript
let n: number = 3.14;        // one type for ints and floats alike
let s: string = "text";
let b: boolean = true;
let big: bigint = 9007199254740993n;
let sym: symbol = Symbol("id");
let nothing: null = null;
let missing: undefined = undefined;

null and undefined are distinct. Roughly: undefined means "no value was ever set", null means "explicitly set to nothing". Under strictNullChecks (which you should have on) neither is assignable to other types unless you say so:

typescript
let title: string = null;          // error under strict mode
let subtitle: string | null = null; // fine — you declared the possibility

any, unknown and never#

Three special types worth understanding early, because generated code reaches for the wrong one.

any turns the checker off for that value. It is contagious — anything derived from an any is also unchecked — and it is how a codebase quietly loses its type safety.

typescript
let data: any = JSON.parse(raw);
data.wahtever.deeply.nested;      // no error. no help. typo ships.

unknown is the safe version. You can hold anything in it, but you must narrow before you use it:

typescript
let data: unknown = JSON.parse(raw);
data.toUpperCase();                       // error — good
if (typeof data === "string") {
  data.toUpperCase();                     // fine, narrowed to string
}

never is the type with no values — a function that always throws, or a branch that cannot be reached. You will meet it mostly in exhaustiveness checks.

Running it#

TypeScript does not run directly — it is checked, then the types are stripped to produce JavaScript.

shell
npm i -D typescript
npx tsc --init          # creates tsconfig.json
npx tsc                 # type check + emit .js
npx tsc --noEmit        # type check only — what CI should run

Modern Node can also run .ts files directly by stripping types without checking them, which is convenient in development. Checking and running are separate concerns: running your code does not mean it type-checks. Keep tsc --noEmit in your build.

Exercise#

typescript
// Declare, with the right types:
//   - a constant `siteName` holding "Learn TypeScript"
//   - a variable `visitors` starting at 0 that you increment
//   - a variable `lastVisitor` that is a string OR null, starting null
// Then log all three.

// write your code here

Common questions#

Should I annotate everything?#

No — annotate where it adds information. Function parameters and return types are worth annotating because they are a contract other code depends on. Local variables with an obvious initialiser are better left inferred; a redundant annotation is one more thing to keep in sync.

interface or type for object shapes?#

Either. type handles unions and intersections uniformly and is the more common default now; interface supports declaration merging, which matters if you are extending third-party types. Pick one for your codebase and be consistent — the next lesson covers both.

What happened to var?#

It is function-scoped rather than block-scoped and it hoists, which produces bugs that let and const make impossible. It still works because TypeScript never breaks JavaScript, but there is no reason to write it.

Get the TypeScript agent pack

A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for TypeScript. One email, then occasional updates when the tooling shifts. No course pitch.

Unsubscribe in one click. We never sell the list. Or just take the AGENTS.md now — no email needed.