TypeScript is an open-source programming language developed and maintained by Microsoft. It is a superset of JavaScript, meaning that it builds upon JavaScript by adding optional static typing, classes, and interfaces. TypeScript code is compiled (or transpiled) into standard JavaScript code, which can then run in any JavaScript environment, such as browsers or Node.js.
TypeScript supports standard JavaScript types but allows you to specify them explicitly.
Boolean: Represents true/false values.
let isDone: boolean = true;
Number: Represents numeric values.
let decimal: number = 10;
String: Represents text values.
let name: string = "Pratheesh";
Array: Denotes a collection of values of a specific type.
let list: number[] = [1, 2, 3];
let names: Array<string> = ["John", "Doe"];
Tuple: Represents an array with a fixed number of elements, where each element can have a different type.
let tuple: [string, number];
tuple = ["Hello", 42];
Enum: Enum allows defining a set of named constants.
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
Any: A variable can be of any type, useful when you don’t know the type in advance.
let notSure: any = 4;
In TypeScript, if you do not explicitly provide a type, TypeScript will infer the type based on the assigned value.
let myNumber = 10; // TypeScript infers `number`
Function Types: You can specify types for function parameters and return types.
function add(x: number, y: number): number {
return x + y;
}
Optional Parameters: Use ?
to make parameters optional.
function greet(name: string, age?: number): string {
return `Hello, ${name}`;
}
Default Parameters: You can provide default values for parameters.
function greet(name: string = "User"): string {
return `Hello, ${name}`;
}
Interfaces in TypeScript define the structure of an object or a contract that an object must adhere to. This helps ensure that objects follow a specific shape.
interface Person {
name: string;
age: number;
}
let user: Person = { name: "Pratheesh", age: 30 };
Interfaces can also describe function types, classes, and array types.
Classes in TypeScript are similar to classes in object-oriented languages like Java or C#. They allow you to define properties and methods.
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance} meters.`);
}
}
let dog = new Animal("Dog");
dog.move(10);
TypeScript also supports inheritance and modifiers like private
, protected
, and public
.
Generics enable creating reusable components that work with various types.
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("Hello");
Union types allow a variable to take multiple types.
function printId(id: number | string) {
console.log("ID: " + id);
}
You can create custom type aliases for complex types.
type StringOrNumber = string | number;
let id: StringOrNumber = 101;
Sometimes, you need to tell TypeScript that a value has a specific type, even if TypeScript cannot infer it automatically. This is done using type assertions.
let someValue: any = "Hello World";
let strLength: number = (someValue as string).length;
Modules allow you to split your code into multiple files and import/export them.
Exporting:
export class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
Importing:
import { User } from "./User";
Decorators are special kinds of declarations that can be attached to classes, methods, accessors, properties, or parameters to modify their behavior. They are a feature of meta-programming.
function Log(target: any, key: string) {
console.log(`${key} was called`);
}
class Calculator {
@Log
add(x: number, y: number): number {
return x + y;
}
}
What is TypeScript?
What are the benefits of using TypeScript over JavaScript?
What is Type Inference in TypeScript?
Explain the difference between interface
and type
in TypeScript.
interface
is typically used for defining object shapes, while type
can define unions, primitives, and more complex structures. Interfaces can also be extended, while types are more flexible but cannot extend other interfaces.What is a Union Type in TypeScript?
number | string
.What are Generics in TypeScript?
How does TypeScript handle optional parameters in functions?
?
after the parameter name.What is the difference between any
and unknown
in TypeScript?
any
disables all type checking, while unknown
requires the type to be checked before usage.What is the use of readonly
modifier in TypeScript?
readonly
modifier makes a property immutable after it is initialized.What is the use of enum
in TypeScript?
enum
is used to define a set of named constants.How do you define default parameters in TypeScript?
What is type assertion in TypeScript?
Can you explain decorators in TypeScript?
What is module in TypeScript?
What is never
type in TypeScript?
never
type represents a value that never occurs, used typically for functions that throw errors or have infinite loops.What are mapped types in TypeScript?
keyof
operator.How does protected
differ from private
in TypeScript?
private
restricts access to within the class, while protected
allows access within the class and its subclasses.What is strictNullChecks
in TypeScript?
strictNullChecks
is a compiler option that enforces stricter checking for null
and undefined
TypeScript stands in an unusual relationship to JavaScript. TypeScript offers all of JavaScript’s features, and an additional layer on top of these: TypeScript’s type system.
For example, JavaScript provides language primitives like string
and number
, but it doesn’t check that you’ve consistently assigned these. TypeScript does.
This means that your existing working JavaScript code is also TypeScript code. The main benefit of TypeScript is that it can highlight unexpected behavior in your code, lowering the chance of bugs.
This tutorial provides a brief overview of TypeScript, focusing on its type system.
TypeScript knows the JavaScript language and will generate types for you in many cases. For example, in creating a variable and assigning it to a particular value, TypeScript will use the value as its type.
let helloWorld = "Hello World";
let helloWorld: string;
By understanding how JavaScript works, TypeScript can build a type-system that accepts JavaScript code but has types. This offers a type-system without needing to add extra characters to make types explicit in your code. That’s how TypeScript knows that helloWorld
is a string in the above example.
You may have written JavaScript in Visual Studio Code and had editor auto-completion. Visual Studio Code uses TypeScript under the hood to make it easier to work with JavaScript.
You can use a wide variety of design patterns in JavaScript. However, some design patterns make it difficult for types to be inferred automatically (for example, patterns that use dynamic programming). To cover these cases, TypeScript supports an extension of the JavaScript language, which offers places for you to tell TypeScript what the types should be.
For example, to create an object with an inferred type which includes name: string
and id: number
, you can write:
const user = {
name: "Hayes",
id: 0,
};
You can explicitly describe this object’s shape using an interface declaration:
interface User {
name: string;
id: number;
}
You can then declare that a JavaScript object conforms to the shape of your new interface by using syntax like : TypeName
after a variable declaration:
const user: User = {
name: "Hayes",
id: 0,
};
If you provide an object that doesn’t match the interface you have provided, TypeScript will warn you:
interface User {
name: string;
id: number;
}
const user: User = {
username: "Hayes",
id: 0,
};
Object literal may only specify known properties, and
'username' does not exist in type 'User'.
Since JavaScript supports classes and object-oriented programming, so does TypeScript. You can use an interface declaration with classes:
interface User {
name: string;
id: number;
}
class UserAccount {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
const user: User = new UserAccount("Murphy", 1);
You can use interfaces to annotate parameters and return values to functions:
function deleteUser(user: User) {
// ...
}
function getAdminUser(): User {
//...
}
There is already a small set of primitive types available in JavaScript: boolean
, bigint
, null
, number
, string
, symbol
, and undefined
, which you can use in an interface. TypeScript extends this list with a few more, such as any
(allow anything), unknown
(ensure someone using this type declares what the type is), never
(it’s not possible that this type could happen), and void
(a function which returns undefined or has no return value).
You’ll see that there are two syntaxes for building types: Interfaces
and Types
. You should prefer interface
. Use type
when you need specific features.
With TypeScript, you can create complex types by combining simple ones. There are two popular ways to do so: with unions, and with generics.
With a union, you can declare that a type could be one of many types. For example, you can describe a boolean type as being either true or false:
type MyBool = true | false;
A popular use-case for union types is to describe the set of string or number literals that a value is allowed to be:
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
Unions provide a way to handle different types too. For example, you may have a function that takes an array or a string:
function getLength(obj: string | string[]) {
return obj.length;
}
To learn the type of a variable, use typeof
:
Type | Predicate |
---|---|
string | typeof s === "string" |
number | typeof n === "number" |
boolean | typeof b === "boolean" |
undefined | typeof undefined === "undefined" |
function | typeof f === "function" |
array | Array.isArray(a) |
For example, you can make a function return different values depending on whether it is passed a string or an array:
function wrapInArray(obj: string | string[]) {
if (typeof obj === "string") {
return [obj];
}
return obj;
}
Generics provide variables to types. A common example is an array. An array without generics could contain anything. An array with generics can describe the values that the array contains.
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;
You can declare your own types that use generics:
interface Backpack<Type> {
add: (obj: Type) => void;
get: () => Type;
}
// This line is a shortcut to tell TypeScript there is a
// constant called `backpack`, and to not worry about where it came from.
declare const backpack: Backpack<string>;
// object is a string, because we declared it above as the variable part of Backpack.
const object = backpack.get();
// Since the backpack variable is a string, you can't pass a number to the add function.
backpack.add(23);
Argument of type ‘number’ is not assignable to parameter of type ‘string’.
One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.
In a structural type system, if two objects have the same shape, they are considered to be of the same type.
interface Point {
x: number;
y: number;
}
function logPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);
The point
variable is never declared to be a Point
type. However, TypeScript compares the shape of point
to the shape of Point
in the type-check. They have the same shape, so the code passes.
The shape-matching only requires a subset of the object’s fields to match.
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"
const color = { hex: "#187ABF" };
logPoint(color);
Argument of type ‘{ hex: string; }’ is not assignable to parameter of type ‘Point’. Type ‘{ hex: string; }’ is missing the following properties from type ‘Point’: x, y
There is no difference between how classes and objects conform to shapes:
class VirtualPoint {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // logs "13, 56"
If the object or class has all the required properties, TypeScript will say they match, regardless of the implementation details.
JavaScript is dynamically typed, meaning the behavior of values is only known at runtime. Every value in JavaScript has a set of behaviors that can be observed through various operations.
const message = "Hello World!";
// Accessing the 'toLowerCase' property and calling it
message.toLowerCase(); // Output: "hello world"
// Trying to call the variable directly
message(); // TypeError: message is not a function
message()
directly.TypeScript introduces static typing, which allows developers to know potential errors before the code is run. This helps avoid bugs and improve code predictability.
const message = "hello!";
message();
// Error: Type 'String' has no call signatures.
message
in this case).JavaScript does not throw exceptions for every possible mistake. For instance, accessing a property that doesn’t exist returns undefined
instead of throwing an error.
const user = { name: "Daniel", age: 26 };
console.log(user.location); // undefined
However, in TypeScript, this is flagged as a potential bug:
const user = { name: "Daniel", age: 26 };
console.log(user.location);
// Error: Property 'location' does not exist on type '{ name: string; age: number; }'
JavaScript would let this pass, but it would not work as intended:
const announcement = "Hello World!";
announcement.toLocalLowerCase(); // Error: Property 'toLocalLowerCase' does not exist on type 'string'.
What you likely meant was:
announcement.toLocaleLowerCase();
JavaScript wouldn’t flag this as a problem at compile time, but TypeScript will:
function flipCoin() {
return Math.random < 0.5; // Error: Operator '<' cannot be applied to '() => number' and 'number'.
}
The correct function would be:
function flipCoin() {
return Math.random() < 0.5;
}
Errors in basic logic can also be caught:
const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
// ...
} else if (value === "b") {
// Error: This comparison appears to be unintentional because the types 'a' and 'b' have no overlap.
}
TypeScript isn’t just about catching errors—it also provides tooling support for code completions and auto-suggestions.
import express from "express";
const app = express();
app.get("/", function (req, res) {
res.send; // Auto-suggestions: send, sendDate, sendFile, etc.
});
app.listen(3000);
tsc
)TypeScript’s compiler (tsc
) compiles .ts
files into JavaScript. For example, writing a simple “Hello World” program in TypeScript looks identical to JavaScript:
// hello.ts
console.log("Hello world!");
Compile the file:
tsc hello.ts
This produces a hello.js
file:
// hello.js
console.log("Hello world!");
Introducing a type-checking error would look like this:
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Brendan");
// Error: Expected 2 arguments, but got 1.
TypeScript catches the error and prevents the JavaScript from being generated if you use strict settings (--noEmitOnError
).
TypeScript allows us to explicitly define types. For example:
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date()); // Correct usage
In many cases, TypeScript can infer the type, so we don’t always need to annotate variables:
let msg = "hello there!"; // TypeScript infers msg as a 'string'
TypeScript can transform code that uses modern ECMAScript (ES6 or above) features into older versions for better browser compatibility. For instance, template strings are rewritten:
`Hello ${person}, today is ${date.toDateString()}!`;
// becomes
"Hello ".concat(person, ", today is ").concat(date.toDateString(), "!");
This is called downleveling.
You can specify which version of ECMAScript TypeScript should target:
tsc --target es2015 hello.ts
This outputs code targeting ECMAScript 2015 (ES6).
TypeScript offers various levels of strictness for type-checking. By default, TypeScript tries not to get in the way, but you can enable stricter settings for catching more bugs. The two most important flags are:
This flag ensures that no variable has an implicit any
type, forcing the developer to specify a type.
function greet(person) {
// Error: Parameter 'person' implicitly has an 'any' type.
}
By default, null
and undefined
are assignable to any type. This can lead to many bugs, but enabling strictNullChecks
helps prevent this by enforcing explicit handling of null
and undefined
.
let name: string = null;
// Error: Type 'null' is not assignable to type 'string'.