Intro to TypeScript & Strong Typing Concepts
Reviewed & published by Brayan K
Master TypeScript's type system to catch bugs at compile-time, enable better tooling, and write more maintainable code. Learn types, interfaces, generics, and utility types.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
TypeScript requires compilation to JavaScript. To practice:
- Use the TypeScript Playground for instant browser testing
- Install TypeScript: npm install -g typescript
- Compile with: tsc filename.ts
What You'll Learn
- Type annotations
- Interfaces & type aliases
- Type narrowing & guards
- Utility types (Partial, Pick, Omit)
- Mapped & conditional types
Why TypeScript?
JavaScript is dynamically typed — variables can hold any value, functions accept anything, and errors only appear at runtime. TypeScript adds a compile-time type system that catches bugs before your code runs.
JavaScript Problems
- • Runtime errors in production
- • No autocomplete for object shapes
- • Wrong argument types accepted
- • Undefined property access
- • Risky refactoring
TypeScript Solutions
- ✓ Compile-time error detection
- ✓ Full IDE autocomplete
- ✓ Type-safe function calls
- ✓ Guaranteed property access
- ✓ Safe, confident refactoring
Basic Type Annotations
Type annotations tell TypeScript exactly what kind of value a variable, parameter, or return value should hold.
// TypeScript Basic Type Annotations
// Primitive types
let name: string = "Boopie";
let age: number = 16;
let alive: boolean = true;
// Arrays
let scores: number[] = [10, 20, 30];
let names: string[] = ["Alice", "Bob"];
let mixed: (string | number)[] = [1, "two", 3];
// Objects
let user: { name: string; age: number } = {
name: "Boopie",
age: 16
};
// Functions with types
function add(a: number, b: number): number {
return a + b;
}
// Arrow functions with types
const multiply = (x: number, y: number): number => x * y;
// Optional parameters
function greet(name: string, title?: string): string {
return title ? title + " " + name : name;
}
// Default parameters
function createUser(name: string, role: string = "user") {
return { name, role };
}
// Type inference - TS figures out the type
let count = 10; // inferred as number
count = "hello"; // ❌ Error! Type 'string' not assignable to 'number'
let items = [1, 2, 3]; // inferred as number[]
console.log(add(5, 10)); // 15
console.log(greet("Alice")); // "Alice"
console.log(greet("Alice", "Dr.")); // "Dr. Alice"Interfaces
Interfaces describe the shape of objects. They define what properties an object must have and their types, making your code predictable and self-documenting.
// Interfaces - Describe Object Shapes
interface User {
name: string;
age: number;
email: string;
active: boolean;
}
// Using the interface
const user: User = {
name: "Boopie",
age: 16,
email: "[email protected]",
active: true
};
// Optional properties
interface Product {
id: number;
name: string;
description?: string; // optional
price: number;
}
const product: Product = {
id: 1,
name: "Widget",
price: 29.99
// description is optional, so we can omit it
};
// Readonly properties
interface Config {
readonly apiKey: string;
readonly baseUrl: string;
}
const config: Config = {
apiKey: "abc123",
baseUrl: "https://api.example.com"
};
// config.apiKey = "new"; // ❌ Error! Cannot assign to readonly
// Extending interfaces
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
const employee: Employee = {
name: "Alice",
age: 30,
employeeId: "E001",
department: "Engineering"
};
// Function types in interfaces
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}
const calc: Calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};Type Aliases & Union Types
Type aliases create reusable type definitions. Union types allow a value to be one of several types, while literal types restrict values to specific options.
// Type Aliases & Union Types
// Type alias for primitives
type ID = string | number;
let userId: ID = 123;
userId = "abc-123"; // Both are valid
// Type alias for objects
type Point = {
x: number;
y: number;
};
const origin: Point = { x: 0, y: 0 };
// Union types - multiple possible types
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";
orderStatus = "approved"; // ✓ OK
// orderStatus = "unknown"; // ❌ Error!
// Literal types
type Size = "sm" | "md" | "lg" | "xl";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function makeRequest(url: string, method: HttpMethod) {
console.log(method, url);
}
makeRequest("/api/users", "GET");
// makeRequest("/api", "PATCH"); // ❌ Error!
// Intersection types - combine types
type Person = {
name: string;
age: number;
};
type ContactInfo = {
email: string;
phone: string;
};
type ContactablePerson = Person & ContactInfo;
const contact: ContactablePerson = {
name: "Alice",
age: 30,
email: "[email protected]",
phone: "555-1234"
};
// Nullable types
type MaybeString = string | null | undefined;
function parseValue(value: MaybeString): string {
return value ?? "default";
}Generics
Generics create reusable, type-safe components that work with any type. They're essential for building libraries, APIs, and flexible utilities.
// Generics - Reusable Type-Safe Code
// Generic function
function identity<T>(value: T): T {
return value;
}
// Usage - type is inferred or explicit
const num = identity(42); // T is number
const str = identity<string>("hi"); // T is string
// Generic with arrays
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]); // number | undefined
const firstStr = firstElement(["a", "b"]); // string | undefined
// Generic interfaces
interface ApiResponse<T> {
data: T;
success: boolean;
message?: string;
}
interface User {
id: number;
name: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Alice" },
success: true
};
// Generic with constraints
function logLength<T extends { length: number }>(item: T): number {
console.log(item.length);
return item.length;
}
logLength("hello"); // ✓ strings have length
logLength([1, 2, 3]); // ✓ arrays have length
// logLength(123); // ❌ numbers don't have length
// Generic classes
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
const numBox = new Box(100);
const strBox = new Box("hello");
// Multiple type parameters
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const result = pair("age", 25); // [string, number]💡 Key Insight: Generics are type placeholders. When you call a generic function, TypeScript infers or you specify the actual type, giving you full type safety without duplicating code.
Type Narrowing & Guards
Type narrowing lets TypeScript understand the specific type within conditional blocks. Discriminated unions are the most powerful pattern for complex data.
// Type Narrowing & Type Guards
// typeof narrowing
function printValue(value: string | number) {
if (typeof value === "string") {
// TS knows value is string here
console.log(value.toUpperCase());
} else {
// TS knows value is number here
console.log(value.toFixed(2));
}
}
// instanceof narrowing
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// "in" operator narrowing
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
// Custom type guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // TS knows it's a string
}
}
// Discriminated unions - the BEST pattern
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Circle | Rectangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size * shape.size;
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
}
}
// Exhaustiveness checking with never
function assertNever(value: never): never {
throw new Error("Unexpected value: " + value);
}
function getAreaStrict(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size * shape.size;
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
default:
return assertNever(shape); // Error if new shape type added
}
}Worked Example: All Four Ideas in One Program
⚠️ How to run TypeScript. Browsers cannot execute TypeScript — a type annotation like let age: number is a syntax error to a plain JavaScript engine, including the editor on this page. TypeScript is compiled first: tsc checks the types, strips them out, and emits ordinary JavaScript. Two easy ways to run everything below: paste it into the TypeScript Playground at typescriptlang.org/play, which compiles as you type and has its own console, or install it locally with npm install -g typescript and run tsc demo.ts && node demo.js. The expected outputs in this lesson are what that second command actually prints.
Interfaces, unions, narrowing and generics are usually taught one at a time, which makes each look like a separate feature. They are not — they are four halves of the same idea, and here they are doing one job together: a shopping basket where the compiler will not let you spell a shipping method wrongly or read a field off a value that might be missing.
// WORKED EXAMPLE - one small program using the four ideas that carry most of
// TypeScript's weight: an interface, a union type, narrowing, and a generic.
// 1) INTERFACE - a name for the shape an object must have.
// Nothing is created at runtime; this exists only to be checked.
interface Product {
id: number;
name: string;
price: number;
tags?: string[]; // the ? makes it optional - it may be missing entirely
}
// 2) UNION TYPE - "one of these exact values", not just any string.
// Misspell "express" anywhere and the compiler stops you.
type Shipping = "standard" | "express" | "collect";
// A lookup table. Record<K, V> says "an object whose keys are K and values V",
// so if you add a fourth Shipping option, TypeScript flags this table too.
const shippingCost: Record<Shipping, number> = {
standard: 3.99,
express: 9.99,
collect: 0
};
// 3) NARROWING - inside each branch, TypeScript knows exactly which case it is.
function describeShipping(method: Shipping): string {
if (method === "collect") {
return "Collect in store, free";
}
// Here TypeScript already knows method is "standard" | "express".
return method + " delivery costs " + shippingCost[method].toFixed(2);
}
// 4) GENERIC - <T> is a type the caller fills in, so one function serves every
// shape without giving up safety (unlike "any", which gives up all of it).
function firstOrNull<T>(items: T[]): T | null {
return items.length > 0 ? items[0] : null;
}
const basket: Product[] = [
{ id: 1, name: "Mechanical keyboard", price: 89.5, tags: ["input", "desk"] },
{ id: 2, name: "USB-C cable", price: 7.5 } // no tags - fine, it is optional
];
// Because firstOrNull was called with Product[], TypeScript knows the result is
// Product | null - so it forces you to handle the null before touching .name.
const first = firstOrNull(basket);
if (first === null) {
console.log("basket is empty");
} else {
console.log("first item: " + first.name); // safe: first is a Product here
}
// An absent optional property reads as undefined, never as a missing variable.
for (const item of basket) {
const tags = item.tags ?? []; // ?? supplies a fallback for null/undefined
console.log(item.name + " (" + tags.length + " tags) £" + item.price.toFixed(2));
}
const total = basket.reduce((sum, item) => sum + item.price, 0);
console.log("subtotal £" + total.toFixed(2));
console.log(describeShipping("express"));
console.log(describeShipping("collect"));
// ✅ Expected output, after tsc worked.ts && node worked.js :
// first item: Mechanical keyboard
// Mechanical keyboard (2 tags) £89.50
// USB-C cable (0 tags) £7.50
// subtotal £97.00
// express delivery costs 9.99
// Collect in store, free
//
// Try breaking it on purpose: change "express" to "expres" in the last few
// lines. Nothing prints, because tsc refuses to compile - the bug is caught
// before the program ever runs. That is the entire pitch for TypeScript.🎯 Your Turn: Type a Bookshelf
Same four ideas, your turn to write them. Three blanks are marked ___: the optional-property marker, the union separator, and the generic's type parameter. Everything else is done. The comments at the bottom show the real compiler messages for the two mistakes people most often make here, so a red squiggle should tell you something useful rather than just being frightening.
// 🎯 YOUR TURN - fill in the three blanks marked ___
// 1) Finish the interface. Every book has a title and a year. The subtitle is
// optional, so mark it with the one character that means "may be missing".
interface Book {
title: string;
year: number;
subtitle___: string; // 👉 replace ___ with ?
}
// 2) Finish the union type: a book is a "paperback", a "hardback" or an
// "ebook", and nothing else. Separate the options with the | character.
type Format = "paperback" ___ "hardback" ___ "ebook"; // 👉 replace both ___ with |
// Already written for you - postage per format.
const postage: Record<Format, number> = {
paperback: 2.5,
hardback: 4.0,
ebook: 0
};
// 3) Finish the generic. It takes an array of ANY type and returns the LAST
// item, or null when the array is empty.
function lastOrNull<___>(items: T[]): T | null { // 👉 replace ___ with T
return items.length > 0 ? items[items.length - 1] : null;
}
const shelf: Book[] = [
{ title: "The Pragmatic Programmer", year: 1999 },
{ title: "Refactoring", subtitle: "Improving the Design of Existing Code", year: 2018 }
];
const newest = lastOrNull(shelf);
if (newest === null) {
console.log("the shelf is empty");
} else {
console.log("newest: " + newest.title + " (" + newest.year + ")");
console.log("subtitle: " + (newest.subtitle ?? "none"));
}
function describe(format: Format): string {
return format + " postage: £" + postage[format].toFixed(2);
}
console.log(describe("hardback"));
console.log(describe("ebook"));
// ✅ Expected output once the blanks are filled and it compiles:
// newest: Refactoring (2018)
// subtitle: Improving the Design of Existing Code
// hardback postage: £4.00
// ebook postage: £0.00
//
// Real compiler messages you may meet on the way:
// Forget the ? on subtitle, and the first book (which has no subtitle) gives
// error TS2741: Property 'subtitle' is missing in type
// '{ title: string; year: number; }' but required in type 'Book'.
// Pass a format that is not in the union, e.g. describe("audiobook"), and
// error TS2345: Argument of type '"audiobook"' is not assignable to
// parameter of type 'Format'.Utility Types
TypeScript includes powerful built-in utility types that transform existing types. These are essential for everyday TypeScript development.
// Utility Types - Built-in Type Transformers
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; ... }
function updateUser(id: number, updates: Partial<User>) {
// Can pass any subset of User properties
}
updateUser(1, { name: "New Name" }); // ✓ OK
// Required<T> - Make all properties required
interface Config {
host?: string;
port?: number;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; }
// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1, name: "Alice", email: "[email protected]",
password: "123", createdAt: new Date()
};
// user.name = "Bob"; // ❌ Error!
// Pick<T, Keys> - Select specific properties
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }
// Omit<T, Keys> - Remove specific properties
type PublicUser = Omit<User, "password">;
// { id: number; name: string; email: string; createdAt: Date; }
// Record<Keys, Type> - Create object type with keys
type UserRoles = Record<string, "admin" | "user" | "guest">;
const roles: UserRoles = {
alice: "admin",
bob: "user"
};
// Exclude<T, U> - Remove types from union
type Numbers = 1 | 2 | 3 | 4 | 5;
type SmallNumbers = Exclude<Numbers, 4 | 5>;
// 1 | 2 | 3
// Extract<T, U> - Extract matching types
type StringOrNumber = string | number | boolean;
type OnlyStrings = Extract<StringOrNumber, string>;
// string
// NonNullable<T> - Remove null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
// ReturnType<T> - Get function return type
function createUser() {
return { id: 1, name: "Alice" };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; }
// Parameters<T> - Get function parameter types
type CreateUserParams = Parameters<typeof createUser>;
// []
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>;
// [string, number]Mapped & Conditional Types
Advanced type transformations let you create new types programmatically. These power the utility types and enable sophisticated type logic.
// Mapped Types & Conditional Types
// Basic mapped type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Real example
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>;
// All properties are now readonly
// Mapped type with transformation
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string; getEmail: () => string; }
// Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Conditional with inference
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Resolved = UnwrapPromise<Promise<string>>; // string
type Same = UnwrapPromise<number>; // number
// Extract array element type
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Elem = ArrayElement<string[]>; // string
// Combining mapped and conditional types
type NonFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
interface Mixed {
name: string;
age: number;
greet: () => void;
}
type DataProps = NonFunctionProperties<Mixed>;
// "name" | "age"
// Deep Partial - recursive utility
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};
interface NestedConfig {
server: {
host: string;
port: number;
};
database: {
url: string;
};
}
type PartialConfig = DeepPartial<NestedConfig>;
// All nested properties are optionalAsync Types & Promises
TypeScript fully understands asynchronous code. Type your async functions, API responses, and error handling for complete safety.
// Async Types & Promise Handling
// Typing async functions
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Using the Awaited utility type
type ResolvedUser = Awaited<ReturnType<typeof fetchUser>>;
// User
// Promise with union types
type ApiResult<T> =
| { success: true; data: T }
| { success: false; error: string };
async function fetchData(): Promise<ApiResult<User[]>> {
try {
const response = await fetch("/api/users");
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: "Failed to fetch" };
}
}
// Using the result with narrowing
async function handleFetch() {
const result = await fetchData();
if (result.success) {
// TS knows result.data is User[]
result.data.forEach(user => console.log(user.name));
} else {
// TS knows result.error is string
console.error(result.error);
}
}
// Generic async function
async function fetchResource<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
// Usage
const user = await fetchResource<User>("/api/user/1");
const products = await fetchResource<Product[]>("/api/products");
// Promise.all with types
async function fetchMultiple() {
const [users, posts] = await Promise.all([
fetchResource<User[]>("/api/users"),
fetchResource<Post[]>("/api/posts")
]);
// users: User[], posts: Post[]
return { users, posts };
}
// Type for event callbacks
type EventHandler<T> = (event: T) => void | Promise<void>;
interface ClickEvent {
x: number;
y: number;
target: HTMLElement;
}
const handleClick: EventHandler<ClickEvent> = async (event) => {
console.log("Clicked at", event.x, event.y);
};Decorators
Decorators are a meta-programming feature for modifying classes, methods, and properties. They're used extensively in Angular, NestJS, and TypeORM.
// Decorators - Metaprogramming for Classes
// Enable in tsconfig.json: "experimentalDecorators": true
// Class decorator
function Logger(constructor: Function) {
console.log("Class created:", constructor.name);
}
@Logger
class User {
constructor(public name: string) {}
}
// Logs: "Class created: User"
// Decorator factory - returns a decorator
function Component(config: { selector: string }) {
return function(constructor: Function) {
console.log("Component:", config.selector);
};
}
@Component({ selector: "app-header" })
class HeaderComponent {}
// Method decorator
function Log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(5, 3);
// Logs: "Calling add with: [5, 3]"
// Logs: "Result: 8"
// Property decorator
function Required(target: any, propertyKey: string) {
// Mark property as required
const requiredProps = Reflect.getMetadata("required", target) || [];
requiredProps.push(propertyKey);
Reflect.defineMetadata("required", requiredProps, target);
}
// Validation decorator example
function Validate(target: any, propertyKey: string) {
let value: any;
const getter = function() {
return value;
};
const setter = function(newVal: any) {
if (newVal === undefined || newVal === null) {
throw new Error(`${propertyKey} cannot be null or undefined`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
}
// Used in: Angular, NestJS, TypeORM, class-validatorDeclaration Files
Declaration files (.d.ts) add type information to JavaScript libraries. Module augmentation lets you extend existing types.
🎯 Mini-Challenge: A Result Type That Cannot Be Misread
Almost every function that can fail returns one of two shapes: it worked and here is the data, or it did not and here is why. In plain JavaScript you keep those two shapes in your head and hope. In TypeScript you write them down once as a discriminated union, and the compiler then refuses to let you read the success fields until you have checked which case you actually have.
No starter code this time — just the brief in the comments. Write it in the TypeScript Playground so you can watch the error appear and disappear as you add the check.
Key Takeaways
Core Concepts
- ✓ Type annotations for safety
- ✓ Interfaces for object shapes
- ✓ Generics for reusable code
- ✓ Union & literal types
- ✓ Type narrowing & guards
Advanced Features
- ✓ Utility types (Partial, Pick, Omit)
- ✓ Mapped & conditional types
- ✓ Decorators for meta-programming
- ✓ Declaration files for JS libs
- ✓ Module augmentation
Practice quiz
What problem does TypeScript solve compared to plain JavaScript?
- It makes code run faster at runtime
- It removes the need for a browser
- It catches type errors at compile-time before the code runs
- It replaces HTML and CSS
Answer: It catches type errors at compile-time before the code runs. TypeScript adds a compile-time type system that catches bugs before your code runs, instead of failing at runtime like dynamically typed JavaScript.
What does an interface describe in TypeScript?
- The shape of an object — its properties and their types
- A CSS layout
- A network request
- A runtime loop
Answer: The shape of an object — its properties and their types. Interfaces describe the shape of objects: what properties an object must have and their types.
In the lesson, what does the '?' do in an interface property like 'description?: string'?
- Makes the property readonly
- Makes the property a union
- Makes the property private
- Makes the property optional
Answer: Makes the property optional. A '?' after a property name marks it as optional, so the object can omit it.
What is a union type such as 'string | number'?
- A type that must be both string and number at once
- A value that can be one of several listed types
- A function that returns nothing
- An array of strings
Answer: A value that can be one of several listed types. Union types allow a value to be one of several possible types, like 'string | number'.
What do generics like 'identity<T>(value: T): T' provide?
- Reusable, type-safe code that works with any type
- Faster compilation
- Automatic network requests
- Runtime type checking only
Answer: Reusable, type-safe code that works with any type. Generics create reusable, type-safe components that work with any type while preserving full type safety.
Which utility type makes all properties of a type optional?
- Required<T>
- Readonly<T>
- Partial<T>
- Pick<T, Keys>
Answer: Partial<T>. Partial<T> makes all properties of T optional; Required<T> does the opposite.
What does 'Omit<User, "password">' produce?
- Only the password property
- The User type with the password property removed
- A union of User and password
- An array of users
Answer: The User type with the password property removed. Omit<T, Keys> removes the specified properties from a type, so Omit<User, 'password'> drops password.
Which narrowing technique works for distinguishing primitive types like string vs number?
- instanceof
- the 'new' operator
- JSON.parse
- typeof
Answer: typeof. typeof narrowing checks primitive types (e.g., typeof value === 'string'); instanceof is for class instances.
In a discriminated union, what is the 'kind' field used for?
- Styling the object
- Acting as a discriminant so TypeScript can narrow to the specific shape
- Storing the object id
- Making the type generic
Answer: Acting as a discriminant so TypeScript can narrow to the specific shape. A literal discriminant property like 'kind' lets a switch narrow a discriminated union to one specific member type.
What is the purpose of a declaration file (.d.ts)?
- To run JavaScript faster
- To compile CSS
- To add type information to JavaScript libraries
- To bundle images
Answer: To add type information to JavaScript libraries. Declaration files (.d.ts) add type information to JavaScript libraries, and module augmentation lets you extend existing types.
Continue this course
- Previous: Module Bundlers: Webpack, Parcel, Vite (Overview)
- Next: Testing JavaScript with Jest & Mocha — Write unit tests, mocks, and assertions with Jest and Mocha
- Quick reference: JavaScript cheat sheet