Frontend · revision notes
TypeScript
Types, interfaces, generics, narrowing and utility types
1Why TypeScript?
Definition
TypeScript = JavaScript + a static type system. Types are checked at compile time and erased at runtime — the output is plain JavaScript.
- Catches typos, wrong arguments and null bugs before running.
- Better editor autocomplete and safe refactors across a big codebase.
- Types document APIs (props, responses, DB models).
Common mistake
Types don't validate data at runtime. JSON from an API is whatever the server sent — validate it with a schema library such as Zod at the boundary.
2Basic types, interface vs type
interface User {
id: number
name: string
email?: string // optional
readonly createdAt: Date
}
type Role = 'admin' | 'doctor' | 'patient' // union of literals
type Staff = User & { role: Role } // intersection
const s: Staff = { id: 1, name: 'Asha', createdAt: new Date(), role: 'doctor' }
const ids: number[] = [1, 2, 3]
const pair: [string, number] = ['age', 31] // tuple
function greet(u: User): string {
return `Hi ${u.name}${u.email ? ' <' + u.email + '>' : ''}`
}
console.log(greet(s), ids.length, pair[0])| interface | type |
|---|---|
Objects and classes; can be extended with extends | Anything: unions, tuples, primitives, mapped types |
| Declaration merging (re-open to add fields) | Cannot be re-opened |
| Good default for object shapes and public APIs | Needed for unions and computed types |
anyturns type checking off — avoid it.unknownis the safe "anything": you must narrow it before use.never= a value that can't exist (exhaustive checks, functions that throw).
3Narrowing and discriminated unions
type Result =
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; message: string }
function render(r: Result): string {
switch (r.status) {
case 'loading': return 'Loading…'
case 'success': return r.data.join(', ') // data exists here
case 'error': return 'Error: ' + r.message
default: {
const unreachable: never = r // compile error if a case is missing
return unreachable
}
}
}
function len(x: string | string[] | null): number {
if (x === null) return 0
return typeof x === 'string' ? x.length : x.length
}
console.log(render({ status: 'success', data: ['a', 'b'] }), len('hello'), len(null))- Narrow with
typeof,instanceof,in, equality and truthiness checks. - A user-defined type guard:
function isUser(x: unknown): x is User { … }.
4Generics
Definition
Generics are type parameters: write one function or type that works for many types while keeping them linked.
function first<T>(items: T[]): T | undefined {
return items[0]
}
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key])
}
interface ApiResponse<T> { data: T; error: string | null }
const users = [{ id: 1, name: 'Asha' }, { id: 2, name: 'Ben' }]
const names = pluck(users, 'name') // string[]
const res: ApiResponse<typeof users> = { data: users, error: null }
console.log(first(names), res.data.length)5Utility types
| Utility | Result |
|---|---|
Partial<T> | every property optional (PATCH bodies) |
Required<T> | every property required |
Readonly<T> | every property readonly |
Pick<T, "a" | "b"> | only those keys |
Omit<T, "password"> | all keys except those |
Record<K, V> | an object with keys K and values V |
ReturnType<typeof fn> | the return type of a function |
Awaited<P> | the value inside a promise |
interface User { id: number; name: string; email: string; password: string }
type PublicUser = Omit<User, 'password'>
type UpdateUser = Partial<Pick<User, 'name' | 'email'>>
type UsersById = Record<number, PublicUser>
// keyof, typeof, and mapped types
type Flags<T> = { [K in keyof T]: boolean }
const config = { port: 3000, host: 'localhost' }
type Config = typeof config // { port: number; host: string }6TypeScript with React
type ButtonProps = {
label: string
variant?: 'primary' | 'ghost'
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
} & React.ComponentProps<'button'>
function Button({ label, variant = 'primary', ...rest }: ButtonProps) {
return <button className={variant} {...rest}>{label}</button>
}
const [user, setUser] = useState<User | null>(null)
const inputRef = useRef<HTMLInputElement>(null)Revision
Types are erased at runtime → validate external data. interface for object shapes, type for unions. Narrow unknown, never use any. Generics keep types linked; Partial / Pick / Omit / Record cover most API types.
7Rapid-fire Q&A
- Q. interface vs type?
- interface describes object shapes, can be extended and merged. type can also express unions, tuples and computed types but can't be re-opened.
- Q. any vs unknown vs never?
- any disables checking; unknown accepts anything but must be narrowed before use; never is a value that cannot exist.
- Q. What are generics?
- Type parameters that let one function or type work with many types while keeping input and output types linked, e.g. first<T>(items: T[]): T.
- Q. What is a discriminated union?
- A union whose members share a literal field (like status). Switching on that field narrows to the right member.
- Q. Does TypeScript check types at runtime?
- No. Types are erased when compiling to JavaScript, so data from APIs must be validated at runtime (e.g. with Zod).
- Q. Partial, Pick and Omit?
- Partial makes all properties optional; Pick keeps chosen keys; Omit removes chosen keys.
- Q. What does keyof do?
- Produces a union of an object type's keys, e.g. keyof { a: 1; b: 2 } is "a" | "b".
- Q. How do you make a switch exhaustive?
- Assign the value to a variable of type never in the default branch; the compiler errors if a case is missing.
- Q. What is a type guard?
- A check that narrows a type — typeof, instanceof, in, or a function returning "x is Type".