Skip to content
RED SAGSkills

TypeScript interview questions and answers

TypeScript questions check whether you can use the type system to catch bugs before they ship. Interviewers look for sensible use of unions, generics and narrowing rather than any-everywhere code.

Below are 32 TypeScript questions with the correct answer and a short explanation, grouped from fresher to experienced level. Try each one before you open the answer.

What TypeScript interviews usually check

  • ✓Types vs interfaces, unions and intersections
  • ✓Narrowing with typeof, in, and discriminated unions
  • ✓Generics and utility types such as Partial, Pick and Record
  • ✓Typing React components, props and hooks
  • ✓Strict mode settings and where any and unknown belong

TypeScript interview questions for freshers easy

1. Type inferred from a let

In TypeScript, what happens with this code?

let qty = 5
qty = "five"
  1. A.It compiles; qty is type any
  2. B.An error: string is not assignable to number
  3. C.It compiles; qty becomes string | number
  4. D.An error: let must have a type annotation
Show answer

Answer: B. An error: string is not assignable to number

TypeScript infers the type of qty from its first value, so qty is number. Assigning a string later is a type error. You only need an annotation when inference cannot work out what you mean.

2. Narrowing a union with typeof

Inside the if block, what type does TypeScript give id?

function show(id: string | number) {
  if (typeof id === "string") {
    return id.toUpperCase()
  }
  return id.toFixed(0)
}
  1. A.string | number
  2. B.number
  3. C.any
  4. D.string
Show answer

Answer: D. string

A typeof check is a type guard. Inside the if, TypeScript narrows id to string, so toUpperCase is allowed. After the return, only number is left, so toFixed is allowed on the last line.

3. Type of an optional parameter

In function send(msg: string, retries?: number), what is the type of retries inside the function?

  1. A.number only
  2. B.number | null
  3. C.number | undefined
  4. D.number | unknown
Show answer

Answer: C. number | undefined

A ? after a parameter makes it optional, and when the caller leaves it out its value is undefined. So TypeScript types it as number | undefined, and strict mode makes you handle the undefined case or give a default like retries = 3.

4. Type of a const string

What type does TypeScript infer for status?

const status = "paid"
  1. A.The literal type "paid"
  2. B.The wider type string
  3. C.The object type String
  4. D.The type any (untyped)
Show answer

Answer: A. The literal type "paid"

A const can never change, so TypeScript gives it the narrowest type, the literal "paid". A let with the same value would be widened to string, because it could be reassigned later.

5. A tuple type

Given let entry: [string, number] = ["GST", 18], which assignment is an error?

  1. A.entry = ["VAT", 5]
  2. B.entry = [18, "GST"]
  3. C.entry[1] = 12
  4. D.entry[0] = "TDS"
Show answer

Answer: B. entry = [18, "GST"]

A tuple fixes the type at each position: index 0 must be a string and index 1 a number. Swapping them breaks both positions. Changing the values while keeping the types is fine.

6. Limiting a prop to fixed values

A button size should only be "sm", "md" or "lg". Which type does this best?

  1. A.type Size = "sm" | "md" | "lg"
  2. B.type Size = string
  3. C.type Size = string[]
  4. D.type Size = { sm: string; md: string; lg: string }
Show answer

Answer: A. type Size = "sm" | "md" | "lg"

A union of string literal types allows exactly those three values, and editors autocomplete them. Any other string, such as "xl" or a typo, becomes a compile-time error.

7. useState in an App Router page

In a Next.js App Router project, a component in app/cart/page.tsx uses useState and an onClick handler, and the build fails. What is the usual fix?

  1. A.Put the interactive part in its own file marked 'use client'
  2. B.Add 'use server' at the top of the page so hooks run on the server
  3. C.Rename the file to page.client.tsx
  4. D.Replace useState with a global variable
Show answer

Answer: A. Put the interactive part in its own file marked 'use client'

Files in the app directory are Server Components by default, and they cannot use state, effects or event handlers. Marking a component file with 'use client' makes it a Client Component. Keeping that boundary small lets the rest of the page stay on the server.

8. What a layout does

What happens to a shared layout.tsx when the user moves between two pages under it?

  1. A.It stays mounted and keeps its state
  2. B.It unmounts and mounts again each time
  3. C.It is fetched again as a full HTML page
  4. D.It is skipped for client-side navigations
Show answer

Answer: A. It stays mounted and keeps its state

Layouts wrap their child pages and are preserved across navigation between those pages. Only the changing page part re-renders, so state like an open sidebar is kept.

9. Default component type

In the app directory, what kind of component is a new page.tsx with no directive?

  1. A.A Client Component
  2. B.A Server Component
  3. C.A static HTML file only
  4. D.An API route handler
Show answer

Answer: B. A Server Component

Everything in app is a Server Component unless marked otherwise. It runs on the server, can be async, and sends no component JavaScript to the browser.

10. What the root layout needs

What must app/layout.tsx (the root layout) include?

  1. A.The <html> and <body> tags
  2. B.A call to useRouter
  3. C.A 'use client' directive
  4. D.An export named getServerSideProps
Show answer

Answer: A. The <html> and <body> tags

The root layout wraps every page and must render html and body itself, since Next.js does not add them. It is a good place for fonts, global styles and providers.

11. TypeScript in new React Native apps

A team starts a new React Native app with the community template or create-expo-app. Which language do they get by default?

  1. A.Flow only
  2. B.TypeScript
  3. C.CoffeeScript
  4. D.Plain JavaScript with no types
Show answer

Answer: B. TypeScript

New React Native and Expo projects are scaffolded in TypeScript, and the core libraries ship type definitions. Types catch mistakes in props, navigation params and API shapes at compile time. Babel strips the types during bundling.

TypeScript intermediate interview questions medium

12. unknown versus any

Why is unknown often a better type than any for data like a parsed API response?

const data: unknown = JSON.parse(text)
data.user.name // compile error
  1. A.unknown forces you to narrow or validate the value before using it, while any turns off type checking
  2. B.unknown makes the code run faster in the browser than any
  3. C.unknown automatically validates the JSON at runtime
  4. D.unknown lets you call any property without errors, like any, but with better autocomplete
Show answer

Answer: A. unknown forces you to narrow or validate the value before using it, while any turns off type checking

With any, every property access and call is allowed, so mistakes reach production silently. With unknown, TypeScript refuses to use the value until you check it, for example with typeof, in, or a schema parser. Neither type exists at runtime; the gain is safety at compile time.

13. Constraining a generic

Which call is a compile-time error?

function longest<T extends { length: number }>(a: T, b: T) {
  return a.length >= b.length ? a : b
}
  1. A.longest("Delhi", "Dubai")
  2. B.longest([1, 2], [3])
  3. C.longest(10, 20)
  4. D.longest({ length: 2 }, { length: 5 })
Show answer

Answer: C. longest(10, 20)

T extends { length: number } means T must have a numeric length property. Strings, arrays and objects with length fit, but numbers do not. The constraint also lets the function body read a.length safely.

14. Type for a PATCH body

A PATCH /users/:id endpoint accepts any subset of name, email and phone, but never id. Given type User = { id: string; name: string; email: string; phone: string }, which type fits?

  1. A.Pick<User, "id">
  2. B.Required<User>
  3. C.Omit<Partial<User>, "name">
  4. D.Partial<Omit<User, "id">>
Show answer

Answer: D. Partial<Omit<User, "id">>

Omit<User, "id"> removes id, and Partial makes every remaining field optional. Pick would keep only id, and Required would force every field. Combining utility types like this avoids writing a second hand-made type.

15. A function that narrows a type

What does the return type value is ApiError do here?

function isApiError(value: unknown): value is ApiError {
  return typeof value === "object" && value !== null && "code" in value
}
  1. A.It checks the shape of ApiError at runtime automatically
  2. B.It casts value to ApiError for the whole function
  3. C.It makes the function return an ApiError object
  4. D.In an if (isApiError(x)) block, x is narrowed to ApiError
Show answer

Answer: D. In an if (isApiError(x)) block, x is narrowed to ApiError

A type predicate tells TypeScript that when the function returns true, the argument is that type. The check itself is your own code, so if it is wrong TypeScript will still trust it. Use it to narrow unknown data safely.

16. Narrowing with the in operator

type Admin = { permissions: string[] } and type Guest = { expiresAt: Date }. In if ("permissions" in user) { ... }, what is user narrowed to?

  1. A.Admin
  2. B.Guest
  3. C.Admin | Guest
  4. D.never
Show answer

Answer: A. Admin

The in operator narrows a union to the members that have (or may have) that property. Only Admin declares permissions, so inside the if user is Admin, and in the else branch it is Guest.

17. Reusing a function’s return type

You want a type for whatever buildInvoice() returns, without writing it by hand. Which is correct?

  1. A.type Invoice = ReturnType<typeof buildInvoice>
  2. B.type Invoice = ReturnType<buildInvoice>
  3. C.type Invoice = typeof buildInvoice()
  4. D.type Invoice = buildInvoice["return"]
Show answer

Answer: A. type Invoice = ReturnType<typeof buildInvoice>

ReturnType needs a function type, and typeof buildInvoice turns the value into its type. Passing buildInvoice directly fails because it is a value, not a type. For async functions, wrap it as Awaited<ReturnType<typeof fn>>.

18. One schema for form and API

In a TypeScript monorepo you define a Zod schema for "create project" and import it in both the React form and the API route. Which statement is true?

  1. A.Once the form has validated, the API can safely trust the body and skip parsing it again
  2. B.Zod only works in the browser, so the API needs a different library
  3. C.The API still parses with the schema; sharing keeps rules and types in sync
  4. D.Sharing the schema exposes your database structure to attackers
Show answer

Answer: C. The API still parses with the schema; sharing keeps rules and types in sync

Client validation improves the experience but anyone can bypass it, so the server parses again with the same schema. Sharing it means error messages, limits and the z.infer types never drift between tiers. The schema describes input shape, not secrets, so shipping it to the browser is fine.

19. Catch-all versus optional catch-all

A docs site uses app/docs/[...slug]/page.tsx. What does a visit to /docs (with no further path) give?

  1. A.The page, with slug as an empty array
  2. B.The page, with slug undefined
  3. C.A redirect to /docs/index
  4. D.A 404, since [...slug] needs one segment or more
Show answer

Answer: D. A 404, since [...slug] needs one segment or more

A catch-all [...slug] matches /docs/a, /docs/a/b and so on, but not /docs itself. The optional catch-all [[...slug]] also matches /docs, with slug undefined.

20. Error in a layout not caught

app/dashboard/layout.tsx throws an error. Why does app/dashboard/error.tsx not catch it?

  1. A.error.tsx only catches errors in route handlers
  2. B.error.tsx wraps that segment's children, not its layout
  3. C.Layouts never throw errors in production
  4. D.The error.tsx file must be in the pages folder
Show answer

Answer: B. error.tsx wraps that segment's children, not its layout

In the hierarchy, a segment's error boundary sits inside its layout. So an error in the layout bubbles up to the parent segment's error.tsx, or to global-error.tsx for the root layout.

21. Error in the root layout

What handles an error thrown in app/layout.tsx?

  1. A.app/global-error.tsx, which must render html and body
  2. B.app/error.tsx in the same folder as the layout
  3. C.app/not-found.tsx in the same folder
  4. D.Nothing; the server crashes in production
Show answer

Answer: A. app/global-error.tsx, which must render html and body

Since the root layout is the top, only global-error.tsx can catch its errors. It replaces the root layout when active, so it must include its own html and body tags.

22. Is proxy enough for auth?

Is a cookie check in proxy.ts enough to protect a dashboard and its Server Actions?

  1. A.Yes; proxy runs before every possible request
  2. B.No; also check auth where data is read or changed
  3. C.Yes; Server Actions skip proxy and are safe
  4. D.No; proxy cannot read cookies at all
Show answer

Answer: B. No; also check auth where data is read or changed

Proxy is good for quick redirects, but matchers can miss paths and config can change. Check the session close to the data, in Server Components, route handlers and every Server Action.

TypeScript interview questions for experienced candidates hard

23. Exhaustive check with never

A teammate adds { kind: "triangle"; base: number; height: number } to the Shape union but forgets to update area. What happens?

type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'square'; side: number }

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return Math.PI * s.r ** 2
    case 'square': return s.side ** 2
    default: {
      const unreachable: never = s
      return unreachable
    }
  }
}
  1. A.The code compiles and area returns undefined for triangles
  2. B.TypeScript adds the missing case automatically
  3. C.Compilation fails at the never assignment, because a triangle is not assignable to never
  4. D.It fails only at runtime with a TypeError
Show answer

Answer: C. Compilation fails at the never assignment, because a triangle is not assignable to never

In the default branch TypeScript narrows s to the union members not handled above. With only circle and square that is never, so the assignment is fine. Once triangle exists, s has the triangle type in default, which cannot be assigned to never, so the compiler points you to every switch that needs updating.

24. Building handler names from keys

What is Handlers?

type Events = "click" | "focus"
type Handlers = `on${Capitalize<Events>}`
  1. A."onclick" | "onfocus"
  2. B.string
  3. C."onClick" | "onFocus"
  4. D."onClick | Focus"
Show answer

Answer: C. "onClick" | "onFocus"

Template literal types build string types from other string types and distribute over unions. Capitalize upper-cases the first letter of each member, so the result is the union of "onClick" and "onFocus".

25. Type of the catch variable

With "strict": true (TypeScript 4.4+), why does this fail to compile?

try {
  await pay()
} catch (err) {
  console.log(err.message)
}
  1. A.catch variables must always be annotated as Error
  2. B.err is unknown, so you must narrow it before reading message
  3. C.await is not allowed inside a try block
  4. D.message is a private field on Error objects
Show answer

Answer: B. err is unknown, so you must narrow it before reading message

strict enables useUnknownInCatchVariables, which types catch variables as unknown, because anything can be thrown, not only Error objects. Check with if (err instanceof Error) before reading err.message.

26. Typing a global on window

An analytics script sets window.dataLayer. In a module file, how do you tell TypeScript about it?

  1. A.type Window = { dataLayer: unknown[] } at the top
  2. B.(window as Window).dataLayer = [] in each file
  3. C.interface Window extends dataLayer {} in the module
  4. D.declare global { interface Window { dataLayer: unknown[] } }
Show answer

Answer: D. declare global { interface Window { dataLayer: unknown[] } }

Inside a module, global declarations must be wrapped in declare global. Because interfaces merge, adding dataLayer to Window extends the built-in type. A type alias cannot merge and would clash with the existing name.

27. Module variable holds the current user

An SSR app on a long-running Node server stores the current user like this. Under load, some users see another person’s name in the header. What is happening?

// lib/current-user.ts
let currentUser: User | null = null

export function setCurrentUser(u: User) { currentUser = u }
export function getCurrentUser() { return currentUser }

// called at the start of every request, then read during render
  1. A.Module state is shared by all concurrent requests in the process; keep per-request data in request scope (cookies()/headers(), AsyncLocalStorage)
  2. B.The CDN is caching the HTML because the variable is exported
  3. C.React hydration replaces the user with the first one it saw
  4. D.TypeScript reuses variables between files when compiled
Show answer

Answer: A. Module state is shared by all concurrent requests in the process; keep per-request data in request scope (cookies()/headers(), AsyncLocalStorage)

A module is loaded once per server process, so every in-flight request shares the same variable. When requests interleave across awaits, one request can overwrite the user while another is still rendering. Read identity from the request itself, or use AsyncLocalStorage or React cache(), which are scoped to one request.

28. Metadata export ignored

A page.tsx starts with 'use client' and exports const metadata = { title: 'Cart' }. What happens?

  1. A.The title works only after hydration
  2. B.The title is set on the server and client both
  3. C.The metadata is moved to the layout by itself
  4. D.It errors; metadata works only in Server Components
Show answer

Answer: D. It errors; metadata works only in Server Components

The metadata export and generateMetadata are supported only in Server Components. Keep page.tsx as a Server Component that sets metadata and renders a client child for the interactive parts.

29. Which loading state shows

app/shop/loading.tsx and app/shop/[id]/loading.tsx both exist. A user navigates from /shop to /shop/42. Which loading UI shows?

  1. A.app/shop/loading.tsx covering the whole page
  2. B.app/shop/[id]/loading.tsx inside the shop layout
  3. C.Both, one after the other, every time
  4. D.Neither, since loading only runs on first load
Show answer

Answer: B. app/shop/[id]/loading.tsx inside the shop layout

Next.js keeps shared layouts mounted and shows the loading boundary closest to what changed. Here the [id] segment changes, so its own loading.tsx shows inside the persistent shop layout.

30. Shape of catch-all params

For app/shop/[[...filters]]/page.tsx, what does params resolve to for /shop/men/shoes and for /shop?

  1. A.{ filters: 'men/shoes' } and { filters: '' }
  2. B.{ filters: ['men', 'shoes'] } and a 404
  3. C.{ filters: ['men', 'shoes'] } and { filters: undefined }
  4. D.{ men: 'shoes' } and { filters: [] }
Show answer

Answer: C. { filters: ['men', 'shoes'] } and { filters: undefined }

Catch-all segments give an array of each path part. The optional form also matches the bare route, where the key is undefined. Code should handle both cases.

31. Proxy runtime in Next.js 16

A team moves middleware.ts to proxy.ts in Next.js 16. What runtime does proxy run on?

  1. A.Edge only
  2. B.Browser
  3. C.Node.js
  4. D.Deno
Show answer

Answer: C. Node.js

In Next.js 16, proxy runs on the Node.js runtime, so full Node APIs are available. The older middleware file kept edge as its default. Keep proxy fast, since it runs before matched requests.

32. Catching broken links at build

The team wants TypeScript to flag <Link href="/prodcts"> as a typo during development. Which Next.js feature helps?

  1. A.Typed routes, which type-check href values
  2. B.The React Compiler setting in next.config
  3. C.The optimizePackageImports setting
  4. D.The serverExternalPackages setting
Show answer

Answer: A. Typed routes, which type-check href values

With typed routes on, Next.js generates route types from the app folder, so Link and router.push with unknown paths fail type checking. It catches broken links before deploy.

Ready to test yourself?

Take a 15-question TypeScript MCQ test with a score at the end, or practise the full TypeScript question bank and earn points on the leaderboard.