LearnLLDTypeScript for LLD

TypeScript for LLD

TypeScript is JavaScript plus one habit: you write down the shape of every value, and a checker reads your code before it runs. That is the whole language. There is no new runtime and nothing new to deploy. Your .ts file is turned into plain .js, and the types are deleted on the way out.

That one habit is why TypeScript is a good language to learn design in. An interface in JavaScript is a promise you keep in your head. In TypeScript it is a line of code the compiler holds you to.

This page is the 20 minutes of TypeScript the rest of the series stands on. If you can read JavaScript, you can read everything below. If you cannot yet, start at JavaScript basics and come back.

Your answer

A function takes a price and a quantity and multiplies them. In JavaScript, what happens if someone calls it with the string '120' and the number 2? What would you want to happen instead?

Set it up once

You need Node and two packages. typescript is the checker, tsx runs a .ts file directly so you do not have to compile by hand while you are learning.

mkdir lld-practice && cd lld-practice
npm init -y
npm install --save-dev typescript tsx
npx tsc --init

Open the tsconfig.json that last command created and make sure these two are on. Every snippet in this series is checked with both.

{
  "compilerOptions": {
    "strict": true,
    "noImplicitOverride": true
  }
}

Then put any snippet from these pages in a file and run it with npx tsx bank.ts. If you do not want to install anything, paste it into the playground at typescriptlang.org/play.

How to read the snippets

Three comment styles repeat on every page. // 240 after a console.log is what that line prints. A line starting with // > is what the line above it printed. And // Error:, on a line or directly under it, marks code the compiler refuses, which is usually the point of the example.

Annotations, and when to skip them

You annotate a variable with a colon and a type. Most of the time you do not have to, because TypeScript works the type out from the value. That is called inference.

let price: number = 120
let dish = 'Masala dosa'
 
price = 135
dish = 42 // Error: Type 'number' is not assignable to type 'string'.

dish was never annotated. TypeScript saw a string go in and decided it is a string for life. The rule of thumb that works: annotate function parameters and return types, let everything else be inferred.

function total(price: number, quantity: number): number {
  return price * quantity
}
 
console.log(total(120, 2)) // 240
total('120', 2)
// Error: Argument of type 'string' is not assignable to parameter
//   of type 'number'.

In JavaScript that last call quietly returns 240 because of string coercion, and the day someone adds instead of multiplies you get '1202' on a customer’s bill. TypeScript stops it at the keyboard.

Describing an object

An object type lists the properties and what each one holds. A ? marks a property that may be missing.

type Dish = {
  name: string
  price: number
  veg: boolean
  note?: string
}
 
const dosa: Dish = { name: 'Masala dosa', price: 120, veg: true }
const idli: Dish = { name: 'Idli', price: 60 }
// Error: Property 'veg' is missing in type
//   '{ name: string; price: number; }' but required in type 'Dish'.
 
console.log(dosa.note) // undefined

There is a second way to write the same thing, and it is the one this series uses most. An interface describes what something can do.

interface Notifier {
  send(message: string): void
}
 
const sms: Notifier = {
  send(message) {
    console.log(`SMS: ${message}`)
  },
}
 
sms.send('Your order is on the way')
// > SMS: Your order is on the way

void means the function returns nothing you should use. Notice that message inside the object needed no annotation. The interface already said it is a string.

type or interface?

They overlap almost completely and people argue about it more than it deserves. The convention on these pages: interface for a behaviour a class promises to provide, type for plain data and for the “one of these” types in the next section.

One of several: unions

A union type says a value is one of a fixed set. With string literals, it replaces most of what you would use an enum for.

type OrderStatus = 'placed' | 'cooking' | 'delivered'
 
let status: OrderStatus = 'placed'
status = 'cooking'
status = 'cancelled'
// Error: Type '"cancelled"' is not assignable to type 'OrderStatus'.
 
function describe(id: number | string): string {
  if (typeof id === 'number') {
    return `Order #${id.toFixed(0)}`
  }
  return `Order ${id.toUpperCase()}`
}
 
console.log(describe(42)) // Order #42
console.log(describe('ab12')) // Order AB12

Look at describe. Inside the if, TypeScript knows id is a number, so toFixed is allowed. After it, the only thing left is a string, so toUpperCase is allowed. This is called narrowing, and you will lean on it in the State and Polymorphism pages.

Collections and the angle brackets

number[] is an array of numbers. For anything else that holds values, the type of the contents goes in angle brackets. Those are generics, and reading them is easier than the name suggests: Map<string, number> is “a Map from string to number”.

const prices: number[] = [120, 60, 40]
const stock = new Map<string, number>()
stock.set('dosa', 12)
 
console.log(stock.get('dosa')) // 12
console.log(stock.get('vada')) // undefined
 
function first<T>(items: T[]): T | undefined {
  return items[0]
}
 
console.log(first(prices)) // 120
console.log(first(['idli', 'vada'])) // idli

first<T> reads as “for whatever type T you hand me an array of, I give back a T”. You wrote the function once and it is correctly typed for numbers, strings and everything else. That T | undefined is honest about the empty array.

The compiler will not let you forget undefined

stock.get can come back empty, and with strict on, TypeScript makes you deal with that before you do arithmetic on it.

const stock = new Map<string, number>([['dosa', 12]])
const left = stock.get('vada')
 
console.log(left + 1) // Error: 'left' is possibly 'undefined'.
console.log((left ?? 0) + 1) // 1

?? means “use the right side if the left is null or undefined”. Half of the production bugs in a JavaScript codebase are this one line, missing.

A class, with types on it

A class bundles data with the functions that work on it. The TypeScript additions are small: fields are declared with their types at the top, and methods are annotated like any other function.

class Cart {
  items: string[] = []
 
  add(item: string): void {
    this.items.push(item)
  }
 
  get count(): number {
    return this.items.length
  }
}
 
const cart = new Cart()
cart.add('Masala dosa')
cart.add('Filter coffee')
console.log(cart.count) // 2

get count() is a getter. It is a method that is read like a property, so you write cart.count and not cart.count().

This Cart has a problem you may have spotted: anyone can reach in and do cart.items = []. Fixing that is the first design idea in the series, and it is where Encapsulation starts.

The picture to keep
A spell checker for your design

A spell checker does not write the essay. It underlines the word you got wrong while you are still typing, not after you have posted it. The red squiggle under total('120', 2) is the same thing for code.

In one line: TypeScript is JavaScript where the shapes are written down and checked before the code runs.
The syntax on this page
let price: number
An annotation. Skip it when the value makes the type obvious.
(price: number): number
Parameter types inside the brackets, return type after them.
type Dish = { ... }
A name for an object shape, or for a union.
note?: string
An optional property. Reading it gives string or undefined.
interface Notifier { ... }
A named set of methods that something promises to have.
'placed' | 'cooking'
A union of literals. The value must be exactly one of these.
Map<string, number>
Generics. The types of what the container holds.
function first<T>(...)
A function that works for any type T and keeps track of which one.
left ?? 0
Fall back to 0 when left is null or undefined.

Read it back

Cover the right side and say the type out loud before you reveal it.

What does this type say?
string[]
(amount: number) => boolean
'upi' | 'card' | 'cash'
Map<string, Dish>
Dish | undefined
() => void

Checkpoint

Checkpoint

1. You write let city = "Pune" with no annotation. What is the type of city?

2. A Map<string, number> is asked for a key it does not have. What does TypeScript say the result is?

3. What happens to your interfaces and type annotations when the code actually runs?

Say this in 60 seconds

TypeScript is JavaScript with the shapes written down. I annotate function parameters and return types and let the rest be inferred. I describe plain data with a type, behaviour with an interface, and a fixed set of options with a union of string literals. Generics are just the type of what a container holds, so Map of string to number. Strict mode forces me to handle undefined before I use a value. And all of it is erased at compile time, so types protect my code from me, not from bad data arriving at runtime.

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.