TypeScript Basics
Table of Contents
- The Problem TypeScript Solves
- Install and Run the Compiler
- Annotations and Inference
- Basic Types
- Arrays and Tuples
- Object Types: type and interface
- Unions and Literal Types
- unknown and any
- Typing Functions
- A Taste of Generics
- tsconfig.json and strict
- Reading Compiler Errors
- Try It Yourself
1. The Problem TypeScript Solves
In Errors and Debugging
you met TypeError: Cannot read properties of undefined. JavaScript only
finds that kind of mistake when the line runs, and maybe only on a user's
phone. JavaScript lets you pass a string where a number is expected, or
misspell a property name, and says nothing until it breaks.
TypeScript is JavaScript with an extra layer: you describe the types of your values, and a tool called the compiler checks your code before it runs.
function double(n: number) {
return n * 2;
}
double("5"); // error found while you type, not when a user clicks
Three facts to remember:
- TypeScript is a superset of JavaScript. All valid JavaScript is valid TypeScript, so you can learn it gradually.
- Browsers and Node cannot run
.tsfiles directly. The compiler turns them into plain.jsfiles. - Types exist only while you write and compile. They disappear from the final JavaScript, so they cost nothing at runtime, and they also cannot protect you at runtime. You will see why in the next chapter.
2. Install and Run the Compiler
TypeScript is installed with npm (from Modules, Node, and npm). In Dara's project folder:
npm init -y
npm i -D typescript
npx tsc --version
-D means a dev dependency: a tool you need while building, not code
the browser loads. npx runs a program installed in the project.
Create hello.ts:
const message: string = "Hello, Dara";
console.log(message);
Compile it:
npx tsc hello.ts
node hello.js
You now have a hello.js next to hello.ts, and Node runs the JavaScript
one. For the rest of the chapter, you can check examples with
npx tsc --noEmit --strict file.ts, which only checks the code and writes
no files. Editors such as VS Code do the same check as you type and show
red underlines.
3. Annotations and Inference
A type annotation is a colon and a type after a name:
let title: string = "Portfolio";
Often you do not need it. TypeScript works out the type from the value. This is called inference:
let year = 2025; // TypeScript infers: number
year = 2026; // fine
year = "2026"; // Error: Type 'string' is not assignable to type 'number'.
A practical rule:
| Where | Annotate? |
|---|---|
| Variables with an obvious starting value | No, let inference work |
| Function parameters | Yes, always |
| Function return types | Optional, but helpful for public functions |
Variables that start empty or with null | Yes |
Hover over any variable in VS Code to see the type TypeScript chose.
4. Basic Types
These match the values you learned in Values, Variables, and Types:
const title: string = "Portfolio";
const year: number = 2025;
const isDone: boolean = false;
const nothing: null = null;
const missing: undefined = undefined;
const big: bigint = 10n;
Notice that the type names are lowercase: string, number, boolean.
The capitalized String and Number are different, older objects. Do not
use them as types.
5. Arrays and Tuples
An array type is the item type followed by []:
const tags: string[] = ["html", "css"];
const scores: Array<number> = [90, 85];
tags.push(42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.
string[] and Array<string> mean the same thing; most people prefer
string[].
A tuple is an array with a fixed length where each position has its own type:
const pair: [string, number] = ["Dara", 25];
const [who, age] = pair; // who: string, age: number
Use tuples for small, fixed groups. For anything with named parts, use an object.
6. Object Types: type and interface
To describe an object, list its properties and their types. This is the shape of one of Dara's projects:
type Project = {
title: string;
year: number;
url?: string; // the ? makes it optional
};
const p: Project = { title: "Quiz App", year: 2025 };
The ? means the property may be missing. Its type becomes
string | undefined, so TypeScript will make you handle the missing case
before you use it:
p.url.toUpperCase(); // Error: 'p.url' is possibly 'undefined'.
p.url?.toUpperCase(); // fine: optional chaining
There is a second way to name an object shape, interface:
interface Person {
name: string;
age: number;
}
interface Student extends Person {
school: string;
}
type | interface | |
|---|---|---|
| Object shapes | Yes | Yes |
| Unions, tuples, simple aliases | Yes | No |
| Extending | & (intersection) | extends |
For everyday work they are almost the same. A simple choice: use type
everywhere, and use interface if a team or library already does. This
series uses type.
7. Unions and Literal Types
A union means "one of these types," written with |:
type ID = string | number;
function format(value: ID): string {
return `ID: ${value}`;
}
A literal type is a type that allows exactly one value, such as
"css". Combine literals in a union and you get a precise list of allowed
values:
type Tag = "html" | "css" | "javascript";
let tag: Tag = "css";
tag = "js"; // Error: Type '"js"' is not assignable to type 'Tag'.
This is one of the most useful features for beginners. A typo like "js"
is caught immediately, and your editor suggests the three valid values as
you type.
8. unknown and any
Sometimes you do not know a value's type, for example data from
JSON.parse. TypeScript has two ways to say so:
let anything: any = "hi";
anything.foo.bar; // no error from TypeScript, crashes when run
let safe: unknown = "hi";
safe.toUpperCase(); // Error: 'safe' is of type 'unknown'.
if (typeof safe === "string") {
console.log(safe.toUpperCase()); // fine: checked first
}
any | unknown | |
|---|---|---|
| Meaning | "Turn the type checker off" | "I don't know yet, so check first" |
| Can you use it directly? | Yes, with no checks | No, you must check its type |
| Safe? | No | Yes |
Avoid any. It hides exactly the bugs TypeScript exists to catch. When a
type is truly unknown, use unknown and narrow it (next chapter).
9. Typing Functions
Annotate parameters and, if you like, the return value:
function add(a: number, b: number): number {
return a + b;
}
const double = (n: number): number => n * 2;
add(1); // Error: Expected 2 arguments, but got 1.
add("1", 2); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Optional parameters use ? and default values work as usual:
function greet(name: string, greeting = "Hello", suffix?: string): string {
return `${greeting}, ${name}${suffix ?? ""}`;
}
A function that returns nothing has the return type void. You can also
write the type of a function value, such as a callback:
let callback: (message: string) => void;
callback = (m) => console.log(m); // m is inferred as string
Read (message: string) => void as "a function that takes a string and
returns nothing."
10. A Taste of Generics
You have already used a generic type: Array<number>. The part in angle
brackets is a type parameter, a placeholder that is filled in later.
Here is a function that works for any kind of array:
function first<T>(items: T[]): T | undefined {
return items[0];
}
const a = first([1, 2, 3]); // a: number | undefined
const b = first(["x", "y"]); // b: string | undefined
T stands for "whatever type the array holds." TypeScript infers it from
the argument, so you rarely write <number> yourself. You will mostly
use generics from libraries (Promise<string>, Array<Project>) before
you write them.
11. tsconfig.json and strict
Instead of command-line options every time, a tsconfig.json file in
the project root holds the settings. Create one:
npx tsc --init
The generated file has many commented options. A small, clean version looks like this:
{
"compilerOptions": {
"target": "ES2023",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
| Option | Meaning |
|---|---|
target | Which JavaScript version to produce |
module | Use import/export in the output |
strict | Turn on all the strictest checks |
rootDir / outDir | Read from src/, write to dist/ |
include | Which files to compile |
Always keep "strict": true. It makes TypeScript complain about
implicit any and about null/undefined where they are not expected.
It feels stricter at first, but those are exactly the mistakes that cause
real bugs. With a tsconfig.json present, run plain npx tsc and it uses
the settings.
12. Reading Compiler Errors
Compiler errors look long, but they follow a pattern. Here is a file with several mistakes:
let year = 2025;
year = "2026";
const p = { title: "x", year: 1 };
p.titel;
function f(x) { return x; }
const el = null;
el.foo;
Running npx tsc --noEmit prints:
bad.ts(2,1): error TS2322: Type 'string' is not assignable to type 'number'.
bad.ts(5,3): error TS2551: Property 'titel' does not exist on type '{ title: string; year: number; }'. Did you mean 'title'?
bad.ts(7,12): error TS7006: Parameter 'x' implicitly has an 'any' type.
bad.ts(10,1): error TS18047: 'el' is possibly 'null'.
Break one down:
bad.ts(5,3): error TS2551: Property 'titel' does not exist ...
└─┬──┘└─┬─┘ └──┬──┘ └──────────────┬─────────────────
file line,column code message
- Read the message first. It names the value and the problem.
- Messages like "X is not assignable to Y" mean "you gave me X where Y was required."
- Look for suggestions such as Did you mean 'title'?
- For long errors, read the first and last lines; the middle usually repeats details.
- The error code (like
TS2322) is handy for searching the web.
Fix errors from the top; one early mistake often causes several later ones.
13. Try It Yourself
- Install and compile. In an empty folder run
npm init -yandnpm i -D typescript. Writehello.ts, compile it withnpx tsc, and run the output withnode. - Inference or annotation? Write
let count = 0;and hover over it. Then trycount = "one"and read the error. - Describe a project. Write a
Projecttype withtitle,year, and an optionalurl. Create two projects, one with a URL and one without, and printurl?.lengthfor both. - Restrict values. Create
type Tag = "html" | "css" | "javascript"and a functiondescribeTag(tag: Tag): string. Try calling it with"python"and read the error. - A generic function. Write
last<T>(items: T[]): T | undefined. Call it with numbers and with strings, and hover over each result. - Turn on the checks. Create a
tsconfig.jsonwith"strict": true. Write a function with an untyped parameter and fix the error it reports.
You now know enough TypeScript to use it on a real project. In the last chapter, we convert Dara's page and see where types help and where they stop: TypeScript in Practice.
Buy Me a Coffee
Found this tutorial helpful? Consider buying me a coffee to support more free content!