TypeScript Refactoring Interview Questions
The React Systems Newsletter
SubscribeSign in
TypeScript Refactoring Interview Questions<br>How to spot weak TypeScript code, explain the problem, and refactor it into safer, cleaner, more maintainable code.
The React Systems Newsletter<br>May 15, 2026
Share
TypeScript interviews are changing.<br>A few years ago, many interviews focused on syntax: what is a union type, how generics work, what Partial does, or how to type a function parameter. Those questions still appear, but stronger interviews now go further. They test whether a developer can look at code that technically works and still notice where it is fragile.<br>Thanks for reading! Subscribe for free to receive new posts and support my work.
Subscribe
That is the real skill.<br>A good TypeScript developer does not only ask:<br>“Does this compile?”<br>They also ask:<br>“Can this represent invalid data?”<br>“Will this still be safe after the next refactor?”<br>“Does this type describe the real domain?”<br>“Is the compiler helping us, or are we fighting it?”<br>“Will another developer understand this in six months?”
1. Narrow Types When TypeScript Infers Too Broadly
One common interview question sounds like this:<br>“TypeScript inferred a type that is too broad. How would you fix it, and why does it matter?”
This is a great question because it tests whether the candidate understands inference, literal types, generics, and practical safety.<br>Here is a weak version:<br>const ageByUsername = new Map()
ageByUsername.set('nina', 31)<br>ageByUsername.set('mark', 'unknown')TypeScript infers:<br>MapThat means the map accepts anything as a key and anything as a value. The compiler is no longer protecting the code.<br>A safer version:<br>const ageByUsername = new Map()
ageByUsername.set('nina', 31)<br>ageByUsername.set('mark', 42)
// Error:<br>// Argument of type 'string' is not assignable to parameter of type 'number'.<br>ageByUsername.set('alex', 'unknown')Now the type communicates intent:<br>keys must be strings
values must be numbers
incorrect values fail during development
The same issue appears often with React state:<br>import { useState } from 'react'
const [paymentStatus, setPaymentStatus] = useState('idle')At first glance, this looks fine. But in many cases TypeScript may infer a broad string, depending on how the state is used. That allows invalid states:<br>setPaymentStatus('banana')<br>setPaymentStatus('something-went-wrong')A better version:<br>type PaymentStatus = 'idle' | 'processing' | 'paid' | 'failed'
const [paymentStatus, setPaymentStatus] =<br>useState('idle')
setPaymentStatus('processing')<br>setPaymentStatus('paid')
// Error<br>setPaymentStatus('banana')This is not just “more typing.” It prevents impossible UI states.<br>A strong interview answer should explain that explicit types are useful when they narrow the possible values and encode business rules.
2. Do Not Annotate What TypeScript Already Knows
Another common interview question:<br>“When should you write an explicit type annotation, and when should you let TypeScript infer it?”
Beginners often over-annotate everything:<br>const username: string = 'nina'<br>const retryCount: number = 3<br>const isDialogOpen: boolean = falseThis is not harmful, but it is noisy. TypeScript already knows these types.<br>A cleaner version:<br>const username = 'nina'<br>const retryCount = 3<br>const isDialogOpen = falseThe important point is this:<br>Use explicit annotations when they add useful information.<br>For example, this annotation is useful:<br>type SubscriptionPlan = 'free' | 'pro' | 'team'
let selectedPlan: SubscriptionPlan = 'free'
selectedPlan = 'team'<br>selectedPlan = 'enterprise' // ErrorWithout the annotation, TypeScript may infer the variable as string in some mutable contexts:<br>let selectedPlan = 'free'
selectedPlan = 'enterprise' // AllowedThat may not be what the application wants.<br>For arrays and maps, inference is often good enough:<br>const productPrices = new Map([<br>['keyboard', 129],<br>['mouse', 79],<br>])TypeScript infers:<br>MapThere is no need to repeat the type unless the empty initialization loses information:<br>const productPrices = new Map()A good rule:<br>Let TypeScript infer obvious implementation details. Add explicit types when they protect boundaries, narrow values, or document public APIs.
3. Protect Function Inputs with Readonly Types
Interview question:<br>“How can you prevent a function from accidentally mutating its input?”
Bad version:<br>type Customer = {<br>id: string<br>name: string
function removeFirstCustomer(customers: Array) {<br>if (customers.length === 0) {<br>return customers
return customers.splice(1)<br>}The problem is splice.<br>It mutates the original array.<br>const customers = [<br>{ id: 'c1', name: 'Nina' },<br>{ id: 'c2', name: 'Mark' },
const remainingCustomers = removeFirstCustomer(customers)
console.log(customers)<br>// Original array was changedA safer version:<br>type Customer = {<br>id: string<br>name: string
function withoutFirstCustomer(<br>customers: ReadonlyArray,<br>): Array {<br>return customers.slice(1)<br>}Now two things are improved:<br>The function...