Learn why TypeScript throws TS2693 when an interface or type alias is used as a value, and how to fix instanceof checks, Object.values and new T().
TypeScript error TS2693 means you used a name that only exists as a type in a place where JavaScript needs an actual value. The classic examples are err instanceof ApiError where ApiError is an interface, or Object.values(OrderStatus) where OrderStatus is a union type alias.
TypeScript keeps two separate namespaces. The type space holds interfaces, type aliases, and generic type parameters — declarations that exist only during type checking and are erased before your code runs. The value space holds variables, functions, classes, and enums — things that survive compilation and exist at runtime. Classes and enums are declared in both spaces at once, which is why they work in either position. An interface never does.
When the checker resolves a name in a value position and only finds a type-space entry, it reports TS2693 instead of "cannot find name", because the name does exist — just not in the space you used it in.
// The general shape of the error:
// 'ApiError' only refers to a type, but is being used as a value here.
// ~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// lives in type space only but this position needs a runtime valueThe mirror image of this error is TS2749, which fires when you use a value where a type belongs and usually suggests typeof.
instanceof Against an Interfaceinstanceof compiles down to a prototype-chain check, so its right-hand side must be a real constructor function. An interface has no prototype — it is gone by the time the check runs.
// ❌ Broken
interface ApiError {
status: number
message: string
}
function handleFailure(err: unknown) {
if (err instanceof ApiError) {
// ~~~~~~~~ Error: 'ApiError' only refers to a type, but is being used as a value here.
console.error("The request failed")
}
}If you only need to narrow the type, write a user-defined type guard that checks the shape:
// ✅ Fixed — a type guard checks the shape at runtime, no constructor needed
interface ApiError {
status: number
message: string
}
function isApiError(value: unknown): value is ApiError {
return (
typeof value === "object" &&
value !== null &&
"status" in value &&
"message" in value
)
}
function handleFailure(err: unknown) {
if (isApiError(err)) {
console.error(`The request failed with ${err.status}`)
}
}If you own the failure objects and actually want instanceof, promote the interface to a class — a class is both a type and a value:
// ✅ Fixed — a class exists in both type space and value space
class ApiError extends Error {
constructor(
public status: number,
message: string,
) {
super(message)
}
}
function handleFailure(err: unknown) {
if (err instanceof ApiError) {
console.error(`The request failed with ${err.status}`)
}
}A union of string literals is a type alias. It tells the compiler which strings are allowed, but it does not create an object you can iterate, so Object.values, Object.keys, and for…of all fail on it.
// ❌ Broken
type OrderStatus = "pending" | "shipped" | "delivered"
const allStatuses = Object.values(OrderStatus)
// ~~~~~~~~~~~ Error: 'OrderStatus' only refers to a type, but is being used as a value here.Declare the values once as a const array and derive the type from it. The array is the runtime artifact; the type stays in sync automatically.
// ✅ Fixed — one source of truth, usable as both a value and a type
const ORDER_STATUSES = ["pending", "shipped", "delivered"] as const
type OrderStatus = (typeof ORDER_STATUSES)[number]
const allStatuses: readonly OrderStatus[] = ORDER_STATUSESnew T() on a Generic Type ParameterGeneric type parameters are erased too. Inside the function body, T is a compile-time placeholder — there is no class sitting behind it to instantiate.
// ❌ Broken
function createRepository<T>(): T {
return new T()
// ~ Error: 'T' only refers to a type, but is being used as a value here.
}Pass the constructor in as a parameter. The type new () => T says "a value you can call with new that produces a T", which gives you both the runtime function and the inferred return type.
// ✅ Fixed — accept the constructor as a value
class OrderRepository {
findAll() {
return []
}
}
function createRepository<T>(Ctor: new () => T): T {
return new Ctor()
}
const repo = createRepository(OrderRepository) // repo: OrderRepositoryContainer registrations, service locators, and DI decorators all store a key at runtime. Interfaces make tempting keys because they describe the contract exactly — but they vanish, so the registration line cannot compile.
// ❌ Broken
interface Logger {
log(message: string): void
}
class ConsoleLogger implements Logger {
log(message: string) {
console.log(message)
}
}
const container = new Map<unknown, unknown>()
container.set(Logger, new ConsoleLogger())
// ~~~~~~ Error: 'Logger' only refers to a type, but is being used as a value here.Use something that exists at runtime as the token — a string or symbol constant, or an abstract class, which keeps the "contract only" feel while still emitting a value:
// ✅ Fixed — an abstract class is a type and a runtime value
abstract class Logger {
abstract log(message: string): void
}
class ConsoleLogger extends Logger {
log(message: string) {
console.log(message)
}
}
const container = new Map<unknown, Logger>()
container.set(Logger, new ConsoleLogger())Ask what you need at runtime. Read the offending line and decide whether the name has to survive compilation. instanceof, new, a function argument, and an object key all need a value; an annotation, a generic argument, and a satisfies clause do not.
Reach for a type guard when you only need narrowing. A function returning value is ApiError gives you the same narrowing instanceof would, without inventing a runtime class. This is the right fix for data that crossed a network or JSON.parse boundary, where nothing is an instance of your class anyway.
Promote the declaration when you do need a value. An interface becomes a class (or abstract class); a string-literal union becomes a const array plus (typeof ARRAY)[number]; a set of named constants becomes an enum. Classes and enums are the two declarations that live in both spaces.
Pass constructors as parameters in generic code. function make<T>(Ctor: new (...args: any[]) => T) is the standard way to instantiate inside a generic factory. Never try to bridge the gap with new (T as any)() — the cast compiles, then throws T is not a constructor at runtime.
Check how the name was imported. If you see TS1361 instead ("cannot be used as a value because it was imported using import type"), the declaration is a class or enum and only the import is type-only — change import type { Order } back to import { Order }.
Keep the runtime list as the source of truth. When you derive a union from a const array rather than writing the literals twice, this error cannot come back: adding a value to the array widens the type in the same commit.
TS2693 occurs when you use a name that only exists in TypeScript's type space — an interface, a type alias, or a generic type parameter — in a position where JavaScript expects a runtime value. Types are erased during compilation, so there is nothing left to evaluate at runtime.
The compiler distinguishes this from TS2304 ("cannot find name") on purpose: the name resolves fine, it is simply in the wrong namespace. If you get the opposite complaint — a value used where a type belongs — that is TS2749, and the fix is usually typeof. Note that library types can trip you up in the same way: z.infer<typeof schema> is a type, so you cannot call .parse on it; call it on the schema value instead.
You cannot: instanceof needs a constructor function that exists at runtime, and interfaces are erased. Either write a user-defined type guard that checks the shape structurally, or turn the interface into a class so it has a runtime counterpart.
interface CartItem {
sku: string
quantity: number
}
function isCartItem(value: unknown): value is CartItem {
return typeof value === "object" && value !== null && "sku" in value
}The type-guard route is usually the better one for data that arrives as JSON, because such objects are plain and would fail an instanceof check even if a matching class existed.
Declare the values first as a const array, then derive the type from it with an indexed access. The array is a real value you can iterate; the type stays in sync automatically.
const PAYMENT_METHODS = ["card", "paypal", "invoice"] as const
type PaymentMethod = (typeof PAYMENT_METHODS)[number]
PAYMENT_METHODS.forEach((method) => console.log(method))The reverse direction — turning an existing union type back into a list of strings — is not possible, because the union does not exist once the compiler is done. If you need both, always start from the array.
Browse all TypeScript practice challenges to keep sharpening your type-level skills.
Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.
Or start solving right away: explore all TypeScript challenges