codejump

AcademyVue

Vue with TypeScript: Generics & Type Guards

A generic <T> only constrains what a function accepts — a type guard is the one construct that actually checks a value's shape at runtime.

2 min readUpdated

3 exercises

Waiting at the end of this page.

A generic type parameter is a contract about the signature, not the body

TypeScript
function uniqueBy<T>(items: T[], key: (item: T) => string): T[] {
  const seen = new Set<string>()
  const result: T[] = []
  for (const item of items) {
    const fingerprint = key(item) // or a bug: JSON.stringify(item) instead
    if (!seen.has(fingerprint)) {
      seen.add(fingerprint)
      result.push(item)
    }
  }
  return result
}

<T> here says "this function works uniformly for any type you call it with" — nothing more. It says nothing about what the function actually does with T internally. A bug where the implementation fingerprints by JSON.stringify(item) instead of by the caller's own key(item) selector is not a type error at all: both are valid strings, so the code compiles and runs, deduping by the wrong criterion. Generics buy you reuse across types; they do not audit correctness within one call.

unknown is the type that forces you to check before you use

Data crossing a real boundary — a JSON response, localStorage, a query param — should be typed unknown, not cast immediately to whatever shape you expect:

TypeScript
function parseUser(raw: unknown): User {
  return raw as User // "trust me" — nothing is actually checked
}

as User convinces the type checker and nothing else. At runtime, raw is whatever it actually is — if that is { id: 2 } with no name, the cast produces a "User" whose .name is undefined, and nothing about the cast itself ever surfaces that.

A type guard is a function both the type checker and the runtime believe

TypeScript
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    typeof (value as Record<string, unknown>).id === 'number' &&
    typeof (value as Record<string, unknown>).name === 'string'
  )
}

function parseUser(raw: unknown): User | null {
  return isUser(raw) ? raw : null
}

The value is User return type is what makes this different from a function that merely returns boolean — inside an if (isUser(raw)) branch, or in the ternary above, TypeScript narrows raw's type to User for the rest of that branch, because the guard's body genuinely inspected the value's shape rather than asserting it.

What interviewers check

Whether as gets named as a real risk, not a routine step — every as cast is a promise to the compiler with nothing behind it at runtime, and the strongest answer is not "avoid as entirely" (sometimes it is the only option) but knowing exactly which casts are load-bearing and validating the ones that cross a real trust boundary.

Now practice it

1 quiz · 2 drills

Reading this page is the cheap half. These are the exercises that make you use it.