Microsoft/TypeScript

"Lookup" Type Broken When Using Generics

Open

#62770 opened on Nov 17, 2025

View on GitHub
 (3 comments) (0 reactions) (0 assignees)TypeScript (48,455 stars) (6,726 forks)batch import
Domain: Mapped TypesHelp WantedPossible Improvement

Description

🔎 Search Terms

"nested lookup with generic", "type lookup with generic"

🕗 Version & Regression Information

  • For doesNotWork1, this changed between versions 3.9.7 and 4.0.5.
  • For other "not working" examples, this is the behavior in every version I tried, and I reviewed the FAQ for entries about generics within lookup tables.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.9.3#code/JYOwLgpgTgZghgYwgAgOrDACwMIHsC2+uIyA3gFDJXIzAQA2AJgIwBcyIArvgEbQDclarQaMATOwDOYKKADmggL7lyoSLEQp0WAMoAHCAmBx6yCAA9IIRpLQYcBIiQrVkkg0ZPsuvAeWWq4NDwSMj6hsb0ADK4uADWnHpkQlTuEV52uh6RSipqwZrIeITEMfGJZpYQ1rbhntGxCUku1AiOxOzaDiUgueRgAJ4GyGVNADwAcnD4KBZWNshxEAO4MEXtIKOJAHzIALzJrgDaANLIoIvLq+s9W3pHUzMAuk8A-OzFTncP0xBPp08lIJyAB6EHICYAeQAKgBRdhYYC2JHIHhQeLVMwAN0xAHd7MgAAZLFZrT6lRqJQnIQAoBJcBkS0vVqZg4LYwLhUSg2oRqpBGMhcJwwDRcFBKtM9PQUFg4CLGJyQLgRbixXFkBy3BBZlB0VBbFAIGBOFAQOQYJwQAgwMBiG4CBBHhAxmc5tUFiTrnVIndtgAKACU7CxuGAApaVDaIGkyFVUDikjYI0pejGAHImZE07sDhHXDQ6Ewk8wADQpfMiJgSZBpnlONNl-PUTMZMQABgAzI3qIpga4ozG4wnq3d03XiNn9ocmwXREmAKzdpuV8TsDMOxEgOQN8uuMHINq6wxgegM2g1DWskWI2wrtyYIVMDjKrmX6AQXfN7IZDtiJfIXsVD3cELA8MA5VtEgUVlEVCUtRgIHPCBGGpSQH04J8+GQEx6FwXFkOQRhOBlTk9HRAwoEGDUhggWw0Lw+RLzlc4RXojCBT4ct9yhOEEUwFEACpcTZATYzVRiLixDsADoAE5pIAdnLAd5VwWiJmVVA1STUcThzacm33P0yNwCjBgDNxv3od4bi+FMASOAAiFt6Ecp5kAAH2QeDENAAi6UkTlmPwUBgHwbhLxRNjMJQQ1AvoHENU5ODrF8kBkNQ0BQkJdKcXFLyfKQlDkDi3AEtopKiUKvyUOkurP1SKz2Gq9LGH-VwVyTFrkPa4RC1XbzUqK-9AK4kDzDAiC7Wgq9ItsVV2NfOAeGlSrD0Na1T3OBDwGAGAz365ESHHEgYDFObLIiWgEC1EVrhJSQxohGF4QuhUKqVFU1XOEg4BABk8skSDsKxOBgHoZbVouKU4AGOR0Xg5TiBjd7JA0sAtPjEcUxdfS81cFz2GYNs2wAFhGoDqH3UDjymqD2Vmm8xMWrDIZIg8xQ2k8GTDPk9oO0Qjo5npYwJHCJXwKVaKenjXqZ1Hny++Mfuw-7kEB4G4FB8G2ZVmG4YR6wkejVT1M0tUO3YXS8Ya2ciyJxdbZXas0z+5VMGgNNy2UZQgA

💻 Code

interface WithCommon {
    field1: number;
    field2: string;
}

interface WithSpecial extends WithCommon {
    special: number;
}

interface SpecialLookup {
    special: WithSpecial;
}

interface CommonLookup extends SpecialLookup {
    common: WithCommon;
}

type Lookup<Name extends keyof CommonLookup> = {
    [K in keyof CommonLookup[Name]]?: CommonLookup[Name][K];
};

// NOTE: this is broken even with `keyof CommonLookup` — key `special` has to be removed to see current error return since with `CommonLookup` we do detect that `special` is not a shared key of all members
function someName<K extends keyof SpecialLookup>(): void {
    const works1: Lookup<'special'> = {
        field1: 1,
        field2: 'common',
        special: 203,
    };

    const works2: Lookup<'common'> = {
        field1: 5,
        field2: 'something',
        // correctly finds that this field should not be there
        special: 32,
    };

    // expectation is that `undefined` should be allowed due to property types showing that it should be
    // NOTE: this *was* working in v3.9.7
    const doesNotWork1: Lookup<K> = {
        // (property) special?: CommonLookup[K]["special"] | undefined — so at minimum this should be resolve to `undefined` since `never | undefined` resolves to `undefined`...
        special: undefined,
        field1: undefined,
        field2: undefined,
    };

    // expectation is that this would be able to correctly identify fields in common for this specific set of keys
    // NOTE: this does not work in any version available in playground
    const doesNotWork2: Lookup<K> = {
        special: 1004,
    }

    // expectation is that this would be able to correctly identify fields in common with all examples
    // NOTE: this does not work in any version available in playground
    const doesNotWork3: Lookup<K> = {
        field1: 15,
        field2: 'another'
    }
}

🙁 Actual behavior

When using a generic as the input to a "lookup" type, we should be able to correctly type the common fields of the "looked-up" interfaces.

What is interesting here is that we appear to get correct behavior from "code completion" (i.e. special is correctly typed as a property of Lookup<K> with type CommonLookup[K]["special"] | undefined), but actually setting the presented type value doesn't work.

🙂 Expected behavior

We should get correct typing for the generic Lookup<K> type:

  • doesNotWork1 should allow undefined as a value since field type evaluates to CommonLookup[K][Field] | undefined.
  • doesNotWork2 should allow number as value for field special since all interfaces within keyof SpecialLookup have special: number on the interface.
  • doesNotWork3 should allow values for field1 and field2 for the same reason as doesNotWork2.

Additional information about the issue

As I called out in my NOTE within the code example, this is also broken in a "simpler" example where the generic is K extends keyof CommonLookup. I presented the above code example because it is the most similar to my use case and I wasn't sure if fixing the "simpler" case would also fix the one where we are choosing a smaller key set.

Contributor guide