TS2344Semantic Error
Since TS 1.0

Fix TS2344: Type Does Not Satisfy the Constraint

Learn why TypeScript throws TS2344 when a type argument violates a generic constraint, and how to fix Pick, Record, ReturnType and keyof T misuse.

error TS2344: Type 'X' does not satisfy the constraint 'Y'

What This Error Means

TS2344 means a generic rejected one of its type arguments. Every type parameter can declare a constraint with extendsPick<T, K extends keyof T>, Record<K extends keyof any, V>, ReturnType<T extends (...args: any) => any> — and when you fill that slot, TypeScript checks your type against the constraint the same way it checks assignability everywhere else. If it does not fit, you get TS2344 and the whole type resolves to an error.

The important detail is where this happens: at the point you supply the type argument, not at the point you use the resulting type. That is what separates TS2344 from its neighbours. TS2322 is a value being assigned to a mismatched type; TS2345 is a value being passed to a mismatched parameter. TS2344 is a type being passed to a mismatched type parameter — so the fix always lives in angle brackets, never in the runtime code below them.

// The general shape of the error:
// Type '"nickname"' does not satisfy the constraint 'keyof UserProfile'.
//      ~~~~~~~~~~~                                  ~~~~~~~~~~~~~~~~~~~
//      what you supplied                            what the extends clause demands

The second half of that message is the one worth reading closely. It is the literal text of the extends clause, so it tells you exactly what shape the generic wants — a key union, an object, a call signature, a string.

Common Causes

1. A Key That Is Not In the Object Type

Pick, Omit and every hand-rolled utility built on K extends keyof T accept only keys that exist on T's static type. A renamed property or a stale interface produces TS2344 even when the property is clearly there at runtime.

// ❌ Broken
interface UserProfile {
  id: string
  email: string
  displayName: string
}
 
type PublicUser = Pick<UserProfile, 'nickname'>
//                                  ~~~~~~~~~~ Error: Type '"nickname"' does not
//                                  satisfy the constraint 'keyof UserProfile'.
// ✅ Fixed — use a key the interface actually declares
type PublicUser = Pick<UserProfile, 'displayName' | 'id'>
 
const publicUser: PublicUser = { id: 'u_1', displayName: 'Ada' }

If the key really should exist, the bug is in the interface, not in the Pick. Add the property to UserProfile and the constraint starts accepting it.

2. An Object Type Where a Key Type Belongs

Record<K, V> takes the keys first and the values second. Swapping the arguments is one of the most common ways to hit TS2344, because K is constrained to keyof any — that is, string | number | symbol — and an interface is none of those.

// ❌ Broken
interface UserProfile {
  id: string
  email: string
}
 
type UsersById = Record<UserProfile, string>
//                      ~~~~~~~~~~~ Error: Type 'UserProfile' does not satisfy
//                      the constraint 'string | number | symbol'.
// ✅ Fixed — keys first, values second
type UsersById = Record<string, UserProfile>
 
const byId: UsersById = { u_1: { id: 'u_1', email: 'ada@example.com' } }

The same reading applies to any constraint printed as string | number | symbol: the generic wants something that can be used as a property key, and you gave it a value type.

3. ReturnType Or Parameters On a Non-Function

These utilities are constrained to (...args: any) => any. Passing an interface, or passing a value name where a type is expected, fails the constraint.

// ❌ Broken
interface UserProfile {
  id: string
  email: string
}
 
type Result = ReturnType<UserProfile>
//                       ~~~~~~~~~~~ Error: Type 'UserProfile' does not satisfy
//                       the constraint '(...args: any) => any'.
//                       Type 'UserProfile' provides no match for the
//                       signature '(...args: any): any'.
// ✅ Fixed — take the type *of the function* with typeof, then unwrap the Promise
interface OrderSummary {
  orderId: string
  total: number
}
 
async function fetchOrders(): Promise<OrderSummary[]> {
  return []
}
 
type Result = Awaited<ReturnType<typeof fetchOrders>>
 
const orders: Result = [{ orderId: 'o_1', total: 42 }]

Writing ReturnType<fetchOrders> without typeof is a different error — TS2749, "refers to a value, but is being used as a type here". If you see that one, adding typeof is the whole fix.

4. Forwarding keyof T Into a Constraint That Wants string

This is the version that bites in library code. Since TypeScript 2.9, keyof T includes number and symbol, so a parameter declared K extends keyof T is not assignable to a constraint of string. Template literal types and string-key utilities trip over this constantly.

// ❌ Broken
interface OrderRow {
  id: string
  total: number
}
 
type ColumnLabel<K extends string> = `column_${K}`
 
type OrderColumns<T, K extends keyof T> = ColumnLabel<K>
//                                                    ~ Error: Type 'K' does not
//                                                    satisfy the constraint 'string'.
//                                                    Type 'keyof T' is not assignable
//                                                    to type 'string'.
// ✅ Fixed — narrow your own parameter to the string keys only
type OrderColumns<T, K extends Extract<keyof T, string>> = ColumnLabel<K>
 
type Labels = OrderColumns<OrderRow, 'id' | 'total'>
 
const label: Labels = 'column_total'

Extract<keyof T, string> keeps exactly the keys that are strings and drops the numeric and symbol ones, which is almost always what the calling code meant anyway. The old keyofStringsOnly compiler flag used to paper over this globally; it was removed in TypeScript 5.5, so narrowing the constraint is now the only supported answer.

How to Fix It

  1. Read the constraint in the message, then hover the generic. The text after "the constraint" is the literal extends clause of the parameter that rejected you. keyof UserProfile means "a key of this object", string | number | symbol means "something usable as a property key", (...args: any) => any means "a function type". Hovering the utility name shows its full signature so you can compare argument by argument — Record<Keys, Values> and Pick<Object, Keys> take their arguments in opposite orders, which is the single most common mix-up.

  2. Supply explicit type arguments one at a time. If a nested alias like type Report = Summarise<Rows<T>, K> fails and you cannot tell which layer is responsible, split it into intermediate aliases. Each alias reports its own TS2344, and the innermost one that errors is the real culprit. The same trick works on generic calls: an inferred argument that violates a constraint surfaces as TS2345 instead, and pinning the type argument converts it into the more precise TS2344 message.

    function firstOf<T extends OrderSummary>(rows: T[]): T {
      return rows[0]
    }
     
    const rawRows = ['not an order']
    firstOf(rawRows) // TS2345 — 'string[]' is not assignable to 'OrderSummary[]'
    firstOf<string>([]) // TS2344 — 'string' does not satisfy the constraint 'OrderSummary'
  3. Tighten your own type parameter instead of loosening the callee's. When the failing argument is a type parameter you declared, the fix belongs in your extends clause: K extends Extract<keyof T, string> for string keys, T extends object for mapped types, F extends (...args: any) => any for anything you will hand to ReturnType. Widening the callee's constraint only works when you own it, and it weakens the guarantee for everyone else.

  4. Don't reach for any or @ts-ignore here. A constraint violation in type-level code cannot be "asserted away" the way a value mismatch can — suppressing the diagnostic leaves the alias resolving to any, silently deleting type safety from everything downstream. If the violation comes from a stale @types package rather than your own code, skipLibCheck: true is a temporary unblock; updating the package is the actual fix.

  5. Keep constraints as narrow as the code needs. The constraints that generate TS2344 are the same ones that make Pick and Record safe. Every time you fix one of these errors by describing your type parameter more precisely — Extract<keyof T, string> rather than keyof T, object rather than unknown — you get better autocomplete and better inference at every call site, and the class of mistake stops recurring.

FAQ

What causes TypeScript error TS2344?

TS2344 fires when a type argument is not assignable to the constraint declared on that type parameter. The compiler checks the argument against the extends clause exactly the way it checks value assignability, and reports the failure at the angle brackets.

The argument can arrive two ways. You can write it yourself:

[object Object]

Or you can forward a type parameter of your own into another generic, in which case the compiler compares the constraint you declared against the constraint the callee wants — not the concrete types that will eventually be substituted in. That is why a perfectly reasonable-looking alias can fail before anyone has instantiated it.

How do I fix Type 'keyof T' does not satisfy the constraint 'string'?

Since TypeScript 2.9, keyof T resolves to string | number | symbol for an unconstrained T, so K extends keyof T is wider than string and fails any constraint that demands string keys. Narrow your own parameter:

type ColumnLabel<K extends string> = `column_${K}`
 
// Before: K may be number or symbol
type ColumnFor<T, K extends keyof T> = ColumnLabel<K> // TS2344
 
// After: K is string-only, and the constraint is satisfied
type ColumnFor<T, K extends Extract<keyof T, string>> = ColumnLabel<K>

Extract<keyof T, string> is the idiomatic fix, and K extends string & keyof T is the equivalent written as an intersection. The keyofStringsOnly flag that used to restore the pre-2.9 behaviour globally was removed in TypeScript 5.5, so do not reach for it.

Why does Pick<T, K> give TS2344 for a key that exists at runtime?

Because Pick constrains K to keyof T — the keys of the static type, which is the only thing the compiler can see. If T is an interface that has drifted from the shape your API actually returns, or if the key came through as plain string rather than a string literal, it is simply not a member of that union.

const keyFromConfig: string = 'displayName'
 
type Selected = Pick<UserProfile, typeof keyFromConfig>
// TS2344 — 'string' is far wider than 'keyof UserProfile'

Fix the source of the type rather than the Pick: declare the key as const so it stays a literal, or add the missing property to the interface. Casting the key type papers over a real mismatch between your types and your data, and the next person to read the alias has no way to tell that it happened.

Related Errors

Practice This

Put your understanding to the test with these related challenges.

Or browse all TypeScript practice challenges to keep sharpening your type-level skills.

Related Concepts

Share this reference

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges