Variables and Types
TL;DR — Use
constby default;letwhen you need to reassign. The primitive types you'll use most arestring,number,boolean,null, andundefined. Type inference means you can skip annotations when the value makes the type obvious — TypeScript figures it out from what you assign.
In this lesson, we'll explore how TypeScript handles variables and its type system. You'll learn about variable declaration, type annotations, and the most common types you'll use in your TypeScript programs.
Variable Declaration
TypeScript supports three ways to declare variables, each with different scoping rules:
// const: Cannot be reassigned (use this by default)
const x = 5;
console.log(`x is ${x}`);
// let: Can be reassigned, block-scoped
let y = 10;
console.log(`y is ${y}`);
y = 15;
console.log(`y is now ${y}`);
// var: Function-scoped (avoid using this - it's from old JavaScript)
var z = 20;
console.log(`z is ${z}`);
Type Annotations
TypeScript allows you to explicitly specify types for your variables:
// Explicit type annotations
const userName: string = "Alice";
const age: number = 30;
const isStudent: boolean = true;
console.log(`${userName} is ${age} years old`);
console.log(`Is student: ${isStudent}`);
Basic Types
TypeScript provides several built-in types to work with:
Primitive Types
String
const greeting: string = "Hello, TypeScript!";
const template: string = `This is a template literal`;
console.log(greeting);
console.log(template);
Number
// TypeScript has only one number type (no separate int/float)
const integer: number = 42;
const float: number = 3.14;
const negative: number = -10;
const hex: number = 0xf00d;
console.log(`Integer: ${integer}`);
console.log(`Float: ${float}`);
console.log(`Negative: ${negative}`);
console.log(`Hex: ${hex}`);
Boolean
const isTrue: boolean = true;
const isFalse: boolean = false;
console.log(`True: ${isTrue}, False: ${isFalse}`);
Special Types
Any
// any disables type checking (use sparingly!)
let anything: any = "a string";
anything = 42;
anything = true;
console.log(`anything can be: ${anything}`);
Unknown
// unknown is a type-safe alternative to any
let uncertain: unknown = "some value";
// You must check the type before using it
if (typeof uncertain === "string") {
console.log(`uncertain is a string: ${uncertain}`);
}
The typeof operator here evaluates at runtime to a string naming the value's type — "string", "number", "boolean", and so on. That is why typeof uncertain === "string" is a plain string comparison, and it is the same check you will reuse as a type guard when narrowing union types later on.
Null and Undefined
const empty: null = null;
const notDefined: undefined = undefined;
console.log(`Null: ${empty}, Undefined: ${notDefined}`);
Type Inference
TypeScript can automatically infer types based on the assigned value:
// TypeScript infers these types automatically
const inferredString = "Hello"; // type: "Hello" — a const keeps the literal type
const inferredNumber = 42; // type: 42
const inferredBoolean = true; // type: true
console.log(`Inferred string: ${inferredString}`);
console.log(`Inferred number: ${inferredNumber}`);
console.log(`Inferred boolean: ${inferredBoolean}`);
// Type inference works with arrays too
const numbers = [1, 2, 3, 4, 5]; // type: number[]
const mixed = [1, "two", 3]; // type: (string | number)[]
console.log(`Numbers: ${numbers}`);
console.log(`Mixed: ${mixed}`);
Look closely at the first three: inferredString is not string, it is the literal type "Hello". That is not a quirk — it follows from const. A const binding can never be reassigned, so "Hello" is the only value it will ever hold, and the narrowest true type is the literal itself. Write the same initializer with let and the compiler must allow a later reassignment, so it widens the inference to string. Same value, different type, because the binding makes a different promise.
Keeping the literal is what makes the precise parts of TypeScript work: it is why a const can satisfy a literal type like "hello" (see below), why discriminated unions can tell their members apart by a kind field, and why as const exists to extend the same treatment to objects and arrays — which do widen their contents by default, as numbers and mixed above show.
Predict
No annotation is written on this array — the compiler infers its type from the elements. What type does TypeScript infer for mixed?
const mixed = [1, "two", 3];Type Assertions
Sometimes you know more about a value's type than TypeScript does:
// Type assertion using 'as'
const someValue: unknown = "this is a string";
const stringLength: number = (someValue as string).length;
console.log(`String length: ${stringLength}`);
Literal Types
You can use specific literal values as types:
// Literal types
const constantString: "hello" = "hello";
const constantNumber: 42 = 42;
const constantBoolean: true = true;
console.log(`Literals: ${constantString}, ${constantNumber}, ${constantBoolean}`);
Recall
Reaching back to Lesson 1, no scrolling: Lesson 1 said TypeScript can figure out a type on its own when the value makes it obvious. What is that feature called, and what does it let you leave out?
Try It Yourself
Practice using different types in the playground below:
// Create variables with different types
const username: string = "Alice";
const score: number = 95.5;
const isPassing: boolean = score >= 60;
console.log(`Student: ${username}`);
console.log(`Score: ${score}`);
console.log(`Passing: ${isPassing}`);
// Type inference example
const colors = ["red", "green", "blue"];
const firstColor = colors[0];
console.log(`Colors: ${colors}`);
console.log(`First color: ${firstColor}`);
// Working with numbers
const a = 10;
const b = 20;
const sum = a + b;
console.log(`${a} + ${b} = ${sum}`);
Try these exercises:
- Create variables of different types and experiment with type annotations
- Try removing type annotations and see how TypeScript infers types
- Create an array of strings and access its elements
- Use template literals to create formatted output
- Try assigning a value of the wrong type and observe the error
Remember: TypeScript's type system helps catch errors before your code runs. Use type annotations when they make your code clearer, and let TypeScript infer types when it's obvious!
Reading about types is not the same as working with them. This is a build task: a small program that reports its own pass/fail. You are given three fixed values and three unfinished const declarations, each one leaning on something this lesson taught — typeof, template literals, and a comparison that returns a boolean. Run it as-is and it fails immediately, telling you which value is still wrong. Fill each declaration in until every check passes and it prints All checks passed.
Nothing above spells out all three answers in one place — you've seen typeof, template literals, and comparison operators as separate ideas, so you'll assemble them yourself. The starter already has the data, the placeholders, and the checks; you replace only the right-hand side of each const.
Build
Finish the build. Three const declarations start out as undefined placeholders, and the checks below them fail until each one holds the right value. Run it as-is to see which check fails first, decide what that value should be, then replace each placeholder until it prints 'All checks passed.' The checks run top to bottom, so the first failure you see is TODO 1 — fix it first, then work down.
import assert from "node:assert";
// The data: values to inspect. Do NOT change these.
const label: string = "widget";
const price: number = 12;
const stock: number = 3;
// TODO 1: the type of price as a string, using typeof.
// typeof price -> "number"
const priceType: string | undefined = undefined; // replace undefined with your code
// TODO 2: a one-line summary from label and price, using a template literal.
// "widget" and 12 -> "widget costs 12"
const summary: string | undefined = undefined; // replace undefined with your code
// TODO 3: whether stock is greater than 0, as a boolean.
// 3 > 0 -> true
const inStock: boolean | undefined = undefined; // replace undefined with your code
// --- Build checks: these must all pass. Do not edit below this line. ---
assert.strictEqual(
priceType,
"number",
"priceType should be the type of price as reported by typeof",
);
assert.strictEqual(
summary,
"widget costs 12",
"summary should combine label and price into one string via a template literal",
);
assert.strictEqual(
inStock,
true,
"inStock should be a boolean for whether stock is greater than 0",
);
console.log("All checks passed.");
console.log("Type of price:", priceType);
console.log("Summary:", summary);
console.log("In stock:", inStock);Expected output: All checks passed.
Type of price: number
Summary: widget costs 12
In stock: true
Once it passes, try two variations and predict each before running:
typeofon a different value. ChangepriceTypetotypeof labelinstead oftypeof price. Before you run it, decide whattypeofreports for a string and which check breaks — the first assert wants"number", so a"string"there is an instructive failure that proves you know which value produces whichtypeofresult.- Loose value, strict check. Change
inStocktostock === "3"— comparing the number3to the string"3"with strict equality. Predict whetherinStockcomes outtrueorfalsebefore running, then check:===compares type as well as value, so a number and a string are never strictly equal —inStockisfalseand the boolean check fails.
Key Takeaways
- Use
letfor mutable variables andconstfor values that never change - TypeScript's core types include
string,number,boolean,null,undefined, andany - Type inference means you don't always need explicit annotations — TypeScript figures it out
- You saw a union appear by inference —
[1, "two", 3]is(string | number)[]; writing unions yourself comes in 06-interfaces-and-types
When your data has more structure than a single variable, you'll want to group types together — see Interfaces and Types for how TypeScript models objects and Type Narrowing for working safely with union types at runtime.
Next Steps
You've learned how to declare variables and annotate them with types. Next, you'll see how to use those types in functions — defining typed parameters, return values, and arrow functions.
Next lesson
Functions
Learn how to write typed functions in TypeScript with optional parameters, overloads, and return types for safer, documented code.
20 min