Type alias vs interface - when and how to use
Use type when:
- You need to define primitives, unions, tuples, or intersections
- You prefer more concise syntax
- You don’t need declaration merging
Use interface when:
- You want to describe object shapes
- You need declaration merging (extend interfaces across files)
- You’re working with classes (implements InterfaceName)
// Type alias
type Point = {
x: number;
y: number;
};
// Interface
interface DuckPoint {
x: number;
y: number;
}Define variable that can be either number or string
let someUnion: number | string;
Define variable n that has the same type as some variable s.
let n: typeof s;
type Person = { age: number; name: string; alive: boolean };
Define new type that is:
- the same as age field in Person (number)
- union of field names of Person ( "age" | "name" | "alive"
- union of all types of Person (string | number | boolean)
type I1 = Person["age"]; // number type I3Keys = keyof Person; // "age" | "name" | "alive" type I3 = Person[keyof Person]; // string | number | boolean
const MyArray = ["a", "b"];
Define type that is the same as elements hold by MyArray (i.e. string)
type elemType = (typeof MyArray)[number];
What is duck typing in typescript, give some example.
“If it looks like a duck and quacks like a duck, it’s a duck.”
TypeScript uses structural typing, meaning an object is compatible with a type as long as it has the required properties, regardless of its explicit name.
interface DuckPoint {
x: number;
y: number;
}
function logPoint(p: DuckPoint) {
console.log(`${p.x}, ${p.y}`);
}
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // Works! z ignoredYou want to define a function that will narrow the type of the variable (variable is your custom object).
How is such function called?
How to define it
Type Guard
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
const underWater1: Fish[] = zoo.filter(isFish);When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union (because we can narrow the type using this literal)
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
// This should never be reached, hence never type!
// If we add some type to Shape union in the future, this will crash (useful)
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}Type casting
* Simple
* Complex (i.e. when simple is not possible and we get a compiler error).
fixedString as any fixedString as any as null;
value.toFixed()
Compiler reports that value is possibly undefined, how to assert that it is not
value!.toFixed()
Function definition:
Arguments are name (string), and number of boolean values (input).
function readButtonInput2(name: string, ...input: boolean[]) {
// ...
}Function definition:
First argument c is a constructor function for Base class.
Constructor Signatures
function greet(c: new () => Base) {
const instance = new ctor();
instance.printName();
}How function overloads work in typescript, write example.
// Overload signatures
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
// Implementation signature
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
// Implementation signature can't be called directly.
// The implementation signature must be compatible with the overload signatures.
// Rule: Always prefer parameters with union types instead of overloads when possible.
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}Write a generic function with variadic argument type T
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}Typed variable f holding the following function:
const f = function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}const f: <T>(arg: T[]) => T | undefined
Define a generic interface with single type.
interface Box<Type> {
contents: Type;
}Define a function, where argument must be something with length: number property (two methods).
With Generic Constraints
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length); // We know it has a .length property
return arg;
}With Duck Typing
function loggingIdentity_2(arg: { length: number }) {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}Generic function taking constructor of a class derived from Box as an argument
function createInstance<A extends Box>(c: new () => A): A {
return new c();
}Define a generic function taking obj and key.
Ensure (with generics) the key to be a valid field of obj
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
// The keyof operator takes an object type and produces a string or numeric literal union of its keys
return obj[key];
}Define a conditional type Flatten<T> that returns type of element if array is given, or type itself if any other value is given.
type Flatten<T> = T extends any[] ? T[number] : T;
Use cases and example of infer keyword
infer keyword compliments conditional types and cannot be used outside an extends clause.infer is used within conditional types to declare a type variable within our constraint to capture types dynamically within the extends clause of a conditional type.type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;
Mapped Types - what is it?
Mapped types allow you to create new types by transforming the properties of an existing type into a different format.
// To add, use + insteaed of -
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
// Remove optional flag
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};Define class Engineer that adheres to Person interface.
class Engineer implements Person {
name: string;
age?: number;
formatName: () => string;
}How combine two interfaces into one
// Extends
interface Dog extends Person, PaintOptions {
// ...
}
// Intersection
// Similar to extends but differs in way how conflicts are handled.
type CombinetType = Person & PaintOptions;