Understanding "Type X is not assignable to type Y"
Attempting to assign a value of one type to a variable of another type.
let jonSnowAge: number = "twenty-five";
// Error: Type 'string' is not assignable to type 'number'.
Interpret and Fix: Jon Snow's age should be a number, not a string.
let jonSnowAge: number = 25; // Corrected
The assigned object doesn't match the expected shape
interface Character {
name: string;
house: string;
}
let tyrion: Character = {
name: "Tyrion Lannister",
title: "Hand of the Queen"
};
// Error: Object literal may only specify known properties,
// and 'title' does not exist in type 'Character'
Interpret and Fix: Jon Snow's age should be a number, not a string.
Depends on your design, it can either be fixed in one of the following ways:
//1. Adjust Object
let tyrion: Character = {
name: "Tyrion Lannister",
house: "Lannister"
};
//2. Update Interface:
interface Character {
name: string;
house: string;
title?: string; // Make title an optional property
}
Mismatch between function parameters or return types and their declared types
function getNumberOfDragons(): number {
return "Drogon, Rhaegal, Viserion";
}
// Error: Type 'string' is not assignable to type 'number'.
Interpret and Fix: Correct the function to return the number of dragons, not their names.
function getNumberOfDragons(): number {
return 3; // Corrected
}
Assigning an array or tuple to a type that expects a different structure or element types.
let starkChildren: [string, string, string, string] =
["Robb", "Sansa", "Arya", 12];
// Error: Type 'number' is not assignable to type 'string'.
Interpret and Fix: Ensure all elements in the tuple are of the correct type, correcting Arya's age to her name.
let starkChildren: [string, string, string, string] = ["Robb", "Sansa", "Arya", "Bran"]; // Corrected
Assigning a value to an enum type that does not exist in the enum.
enum House {
Stark,
Lannister,
Targaryen
}
let jonHouse: House = House['Baratheon'];
// Error: Property 'Baratheon' does not exist on type 'typeof House'.
Interpret and Fix: The House enum does not include Baratheon
. Ensure you're assigning a value from the defined enum options.
let jonHouse: House = House.Stark;
//or
let daenerysHouse: House = House["Targaryen"];
Assigning a value that doesn't fit any type in a union.
type Weapon = 'Sword' | 'Dagger' | 'Bow';
let jonSnowWeapon: Weapon = 'Axe';
// Error: Type '"Axe"' is not assignable to type 'Weapon'.
Interpret and Fix: Ensure the value matches one of the union's types.
let jonSnowWeapon: Weapon = 'Sword';
Using a generic type that does not conform to the constraints or expected structure.
interface Character {
name: string;
house: string;
}
function getCharacterDetail<T extends Character>(detail: T): string {
return detail.name;
}
const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" });
// Error: Argument of type '{ name: string; weapon: string; }' is not assignable to parameter of type 'Character'.
Interpret and Fix: Adjust the input to match the Character interface or extend the interface to include additional properties.
There are a few ways to fix depend on your use cases.
interface Character {
name: string;
house?: string; // Making house optional
weapon: string; // Adding weapon as an optional property
}
function getCharacterDetail<T extends Character>(detail: T): string {
return detail.name;
}
const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" });
interface Character {
name: string;
house: string;
weapon: string;
}
// Adjusting the function to accept a type that is part of Character but without enforcing 'house'
function getCharacterDetail<T extends Partial<Character> & Pick<Character, 'name' | 'weapon'>>(detail: T): string {
return detail.name;
}
const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" });
interface Character {
name: string;
house: string;
weapon: string;
}
// Overload signature for characters without a house
function getCharacterDetail(detail: Omit<Character, 'house'>): string;
// Original function signature
function getCharacterDetail(detail: Character): string;
// Function implementation
function getCharacterDetail(detail: any): string {
return detail.name;
}
const detail = getCharacterDetail({ name: "Arya", weapon: "Needle" });
Challenge
const allegiances = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
// Initial attempt that causes TypeScript to complain
const getAllegiance = (character: string) => {
return allegiances[character];
// Error:expression of type 'string' can't be used to index type '{ "Jon Snow": string; "Tyrion Lannister": string; }'.
};
const allegiances = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
const getAllegianceByKey = (character: keyof typeof allegiances) => {
return allegiances[character]; // Correct
};
console.log(getAllegianceByKey("Jon Snow")); // "Stark"
const allegiances = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
const getAllegianceWithAssertion = (character: string) => {
return allegiances[character as keyof typeof allegiances]; // Correct
};
console.log(getAllegianceWithAssertion("Tyrion Lannister")); // "Lannister"
const allegiances = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
const isKeyOfAllegiance = (key: any): key is keyof typeof allegiances => {
return key in allegiances;
}
const getAllegianceWithGuard = (character: string) => {
if (isKeyOfAllegiance(character)) {
return allegiances[character]; //Correct
}
return undefined; // Or handle the error as appropriate
};
console.log(getAllegianceWithGuard("Jon Snow")); // "Stark"
const allegiances = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
const allegiancesWithIndex: { [key: string]: string } = {
"Jon Snow": "Stark",
"Tyrion Lannister": "Lannister"
};
const getAllegianceWithIndex = (character: string) => {
return allegiancesWithIndex[character]; // Correct
};
console.log(getAllegianceWithIndex("Tyrion Lannister")); // "Lannister"
const weapons = {
"Arya Stark": "Needle",
"Brienne of Tarth": "Oathkeeper"
};
const getWeapon = (hero: string) => {
// TypeScript error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ "Arya Stark": string; "Brienne of Tarth": string; }'.
return weapons[hero];
};
// Specific Key Type
const getAllegianceByKey = (character: keyof typeof allegiances) => allegiances[character];
// Type Assertion
const getAllegianceWithAssertion = (character: string) => allegiances[character as keyof typeof allegiances];
// Type Guard
function isCharacter(character: any): character is keyof typeof allegiances {
return character in allegiances;
}
const getAllegianceWithGuard = (character: string) => isCharacter(character) ? allegiances[character] : 'Unknown character';
// Index Signature
const allegiancesWithIndex: { [key: string]: string } = {
"Jon Snow": "Stark",
"Daenerys Targaryen": "Targaryen"
};
const getAllegianceWithIndex = (character: string) => allegiancesWithIndex[character];
interface Character {
name: string;
title?: string; // The title is optional, hence it could be undefined
}
const character: Character = {
name: "Arya Stark",
// title is not provided
};
const uppercaseTitle = (character: Character): string=> {
return character.title.toUpperCase(); //Error: 'character.title' is possibly 'undefined'.
}
interface Character {
name: string;
title?: string;
}
const arya: Character = {
name: "Arya Stark",
};
const uppercaseTitle = (character: Character): string | undefined =>{ //need to be the union string | undefined
return character.title?.toUpperCase();
}
interface Character {
name: string;
title?: string;
}
const arya: Character = {
name: "Arya Stark",
};
const uppercaseTitle = (character: Character): string =>{
return character.title?.toUpperCase() ?? "Title Unknown";
}
interface Character {
name: string;
title?: string;
}
const arya: Character = {
name: "Arya Stark",
};
const uppercaseTitle = (character: Character): string =>{
if (character.title !== undefined) {
return character.title.toUpperCase();
} else {
return "Title Unknown";
}
}
interface Character {
name: string;
title?: string;
}
const arya: Character = {
name: "Arya Stark",
};
//logical OR (||) operator
const uppercaseTitle = (character: Character): string =>{
const title = character.title || "No Title";
return title.toUpperCase();
}
//or default parameter
const uppercaseTitle = (title: string = "Traveler"): string =>{
return title.toUpperCase();
}
uppercaseTitle(arya.title)
only use it when you're absolutely sure 'title' is not undefined
interface Character {
name: string;
title?: string;
}
const arya: Character = {
name: "Arya Stark",
title: "Princess"
};
const uppercaseTitle = (character: Character): string =>{
return character.title!.toUpperCase();
const holdsIronThrone = (house) => { //Parameter 'house' implicitly has an 'any' type
const ironThroneHolder = "Targaryen";
return house === ironThroneHolder;
}
console.log(holdsIronThrone("Stark"));
//Explicit Types
const holdsIronThrone = (house: string) => {
const ironThroneHolder = "Targaryen";
return house === ironThroneHolder;
}
console.log(holdsIronThrone("Stark")); // No error, returns false
//default value
const holdsIronThrone = (house = "Unknown") => {
const ironThroneHolder = "Targaryen";
return house === ironThroneHolder;
}
console.log(holdsIronThrone("Stark")); // No error, TypeScript infers house is a string from default value
interface House {
name: string;
}
const holdsIronThrone = (house: House) => {
const ironThroneHolder: string = "Targaryen";
return house.name === ironThroneHolder;
}
console.log(holdsIronThrone({ name: "Stark" }));
interface House {
name: string;
}
//Define an interface for the function overloads
interface holdsIronThrone{
(house: string): boolean;
(house: House): boolean;
// Single function implementation that handles all cases, assigned to a variable
const holdsIronThrone : holdsIronThrone= (house: any): boolean =>{
const ironThroneHolder = "Targaryen";
const houseName = typeof house === 'string' ? house : house.name;
return houseName === ironThroneHolder;
}
console.log(holdsIronThrone("Stark"));
console.log(holdsIronThrone({ name: "Targaryen" }));
interface House {
name: string;
sigil: string;
}
const declareAllegiance = (house: House) => {
console.log(`${house.name} pledges loyalty to the realm.`);
};
declareAllegiance({ name: "Stark", sigil: "Direwolf", motto: "Winter is Coming" });
interface House {
name: string;
sigil: string;
}
const declareAllegiance = (house: House) => {
console.log(`${house.name} pledges loyalty to the realm.`);
};
declareAllegiance({ name: "Stark", sigil: "Direwolf"}); //remove motto to align with House interface
Type assertions should be wielded with caution, for they tell TypeScript to trust the developer's knowledge over its own analysis.
interface House {
name: string;
sigil: string;
}
const declareAllegiance = (house: House) => {
console.log(`${house.name} pledges loyalty to the realm.`);
};
declareAllegiance({ name: "Stark", sigil: "Direwolf", motto: "Winter is Coming" } as House);
interface Hero {
name: string;
house: string;
}
const jonSnow: Hero = {
name: "Jon Snow",
house: "Stark",
};
console.log(jonSnow.title); //Error: Property 'title' does not exist on type 'Hero'.
interface HeroWithTitle {
name: string;
house: string;
title: string;
}
const jonSnow: HeroWithTitle = {
name: "Jon Snow",
house: "Stark",
title: "King in the North",
};
console.log(jonSnow.title);
interface Hero {
name: string;
house: string;
}
interface NobleHero extends Hero {
title: string;
}
const jonSnow: NobleHero = {
name: "Jon Snow",
house: "Stark",
title: "King in the North",
};
console.log(jonSnow.title);
interface Hero {
name: string;
house: string;
title?: string; // An optional property
}
const jonSnow: Hero = {
name: "Jon Snow",
house: "Stark",
title: "King in the North",
};
console.log(jonSnow.title);
interface Hero {
name: string;
house: string;
[key: string]: any; //allowing any property
}
const jonSnow: Hero = {
name: "Jon Snow",
house: "Stark",
title: "King in the North",
};
console.log(jonSnow.title);
interface Alliance {
houses: string[];
pact: string;
}
const newAlliance : Alliance = {
houses: ["Stark", "Tully"],
pact: "Mutual Defense",
location: "Riverrun", // Error: Object literal may only specify known properties, and 'location' does not exist in type 'Alliance'.
};
interface Alliance {
houses: string[];
pact: string;
}
interface DetailedAlliance extends Alliance {
location: string;
}
// The new declaration is accepted
const newAlliance: DetailedAlliance = {
houses: ["Stark", "Tully"],
pact: "Mutual Defense",
location: "Riverrun",
};
Sometimes, not all details are known or necessary. We can mark some properties as optional:
interface Alliance {
houses: string[];
pact: string;
location?: string;
}
// The new declaration is accepted
const newAlliance: Alliance = {
houses: ["Stark", "Tully"],
pact: "Mutual Defense",
location: "Riverrun",
};
interface Alliance {
houses: string[];
pact: string;
[key: string]: any;
}
// The declaration, no matter how detailed, is accepted
const newAlliance : Alliance = {
houses: ["Stark", "Tully"],
pact: "Mutual Defense",
location: "Riverrun",
};
Use this tool with caution, you're telling TypeScript to trust our intention.
interface Alliance {
houses: string[];
pact: string;
}
const newAlliance = {
houses: ["Stark", "Tully"],
pact: "Mutual Defense",
location: "Riverrun",
} as Alliance;
//error
interface HouseWithKnights {
name: "Stark" | "Lannister";
knights: number;
}
interface HouseWithCastle {
name: "Tully" | "Greyjoy";
castle: string;
}
const declareAllegiance = (house: HouseWithKnights | HouseWithCastle) =>{}
//Error: Object literal may only specify known properties, and 'knights' does not exist in type 'HouseWithCastle'.
//Fix 1: Aligning Declaration with Expected Interface
declareAllegiance({ name: "Tully", castle: "Riverrun" });
//or
declareAllegiance({ name: "Stark", knights: 100 });
//Fix 2: Creating a More Inclusive Interface
interface NobleHouse {
name: string;
knights?: number;
castle?: string;
}
const declareAllegianceFlexibly = (house: NobleHouse) =>{
console.log(`House ${house.name} has declared their allegiance.`);
}
declareAllegianceFlexibly({ name: "Tully", knights: 100 }); // Now works without error
//Fix 3: Using Type Assertions
declareAllegiance({ name: "Tully", knights: 100 } as HouseWithKnights);
// Fix 4: Extending Interfaces for Complex Scenarios
interface BaseHouse {
name: string;
}
interface HouseWithKnights extends BaseHouse {
knights: number;
}
interface HouseWithCastle extends BaseHouse {
castle: string;
}
type HouseWithKnightsAndCastle = HouseWithKnights & HouseWithCastle;
function declareAllegianceComplex(house: HouseWithKnightsAndCastle) {
console.log(`House ${house.name} with ${house.knights} knights guards ${house.castle}.`);
}
declareAllegianceComplex({ name: "Tully", knights: 100, castle: "Riverrun" });
//error
const sendRaven = (message: string, location: string) => {
console.log(`Sending raven with message: ${message} to ${location}`);
}
sendRaven("White Walkers are coming"); // Error: Expected 2 arguments, but got 1.
//1. Fulfill the arguments
sendRaven("White Walkers are coming", "Winterfell");
//2. give default value for location
const sendRaven = (message: string, location: string = "King's Landing") =>{
console.log(`Sending raven with message: ${message} to ${location}`);
}
sendRaven("All hail the King in the North!");
type Advisor = (name: string, title: string) => string;
const tyrionAdvisor: Advisor = (name: string) => `${name} is the Hand of the Queen`; // Error: Type provides no match for the signature.
//1. fulfill the type of argurments
const tyrionAdvisor: Advisor = (name: string, title: string) => `${name} is ${title}`;
//2. make title optional
type Advisor = (name: string, title?: string) => string;
const spellbook = {
decipher: "Ancient Texts Deciphering Spell",
};
spellbook.decipher(); // Error: Cannot invoke an expression whose type lacks a call signature.
//1. Define the spellbook with a callable function
const spellbook = {
decipher: (text: string) => `Deciphering: ${text}`,
};
spellbook.decipher("Valyrian scrolls");
//2. If the spellbook must remain unchanged, create a separate function to invoke the spell.
const decipherSpell = (text: string) => {
console.log(`Deciphering: ${text}`);
}
decipherSpell(spellbook.decipher);
const sendWarning = (signal: string): string =>{
// No return statement here
}
sendWarning("White Walkers!"); // Error: Function lacks return statement.
//1. Ensure the function fulfills its duty by returning the appropriate warning signal.
const sendWarning = (signal: string): string =>{
return `Warning: ${signal}`;
}
//2. If no specific warning needs to be returned, change the function's return type to void.
const sendWarning = (signal: string): void => {
console.log(`Warning: ${signal}`);
}