이 글에서 정리하는 내용
TypeScript에서 No overload matches this call 오류가 발생했을 때 함수 이름이나 import보다 먼저 확인해야 할 부분을 정리합니다. 함수 overload 후보, 실제 인자 타입, union 타입, DOM·라이브러리 함수 호출에서 자주 막히는 지점을 기준으로 봅니다.
- No overload matches this call 오류가 말하는 것
- 오류 메시지에서 먼저 읽어야 할 부분
- 인자 개수가 안 맞는 경우
- 인자 타입이 안 맞는 경우
- union 타입을 그대로 넘길 때 생기는 문제
- as any로 덮기 전에 확인할 해결 방법
- DOM·라이브러리 함수에서 같은 오류가 날 때 보는 순서
- 정리
No overload matches this call 오류가 말하는 것

TypeScript에서 No overload matches this call 오류를 처음 만나면 메시지가 길게 느껴집니다. 특히 Overload 1 of 2, Overload 2 of 2처럼 여러 후보가 이어지면 어느 줄을 고쳐야 하는지 바로 보이지 않습니다.
이 오류는 함수가 없다는 뜻이 아닙니다. 현재 넘긴 인자 조합으로 호출 가능한 함수 형태를 TypeScript가 찾지 못했다는 뜻입니다. 함수 이름이 맞고 import도 정상이어도, 인자 개수나 타입이 선언된 호출 형태와 어긋나면 같은 오류가 발생합니다.
여기서 overload는 하나의 함수가 여러 호출 형태를 가지는 구조입니다. 어떤 함수는 문자열을 받을 때와 숫자를 받을 때를 나눠 선언할 수 있습니다. TypeScript는 호출부의 값을 보고 그중 하나를 골라야 하는데, 현재 값이 어느 후보에도 정확히 들어맞지 않으면 호출 자체를 막습니다.
겉으로는 맞아 보이지만 실패하는 예
function formatValue(value: string): string;
function formatValue(value: number): string;
function formatValue(value: string | number) {
return String(value);
}
const value: string | number = Math.random() > 0.5 ? "100" : 100;
formatValue(value);
구현부만 보면 formatValue는 string | number를 받고 있습니다. 하지만 호출하는 쪽에서 기준이 되는 것은 위에 선언된 overload 시그니처입니다. 호출부에서는 string을 받는 함수 또는 number를 받는 함수만 선택할 수 있습니다.
value는 string | number입니다. 지금 이 순간 문자열인지 숫자인지 TypeScript가 확정하지 못합니다. 그래서 문자열 overload에도 바로 넣을 수 없고, 숫자 overload에도 바로 넣을 수 없습니다. 이 차이를 놓치면 함수 구현부만 계속 보게 됩니다.
오류 메시지에서 먼저 읽어야 할 부분
오류 메시지가 길 때는 위에서부터 모두 해석하려고 하지 않는 편이 낫습니다. 호출한 함수 이름, TypeScript가 보여주는 overload 후보, 실제로 넘긴 값의 타입을 분리해서 보면 원인이 빨리 좁혀집니다.
Overload 1 of 2와 Overload 2 of 2가 나온다면 TypeScript가 두 가지 호출 후보를 검사했다는 뜻입니다. 하나라도 맞으면 통과하지만, 지금은 두 후보가 모두 실패했습니다. 따라서 마지막 결론이 No overload matches this call로 떨어집니다.
실제 원인은 보통 Argument of type ... is not assignable to parameter of type ... 문장에 들어 있습니다. 이 문장은 “내가 넘긴 값의 타입”과 “함수가 기대한 타입”을 직접 비교합니다. 오류 메시지 전체보다 이 비교 문장을 먼저 잡는 것이 좋습니다.
읽는 순서를 코드 옆에 두고 보면
// 호출한 함수: formatValue(value)
// 함수 후보: formatValue(value: string), formatValue(value: number)
// 실제 인자: value: string | number
// 실패 이유: string이라고도, number라고도 확정되지 않음
이렇게 나누면 오류 메시지가 길어도 방향이 잡힙니다. 함수 전체를 다시 작성하기 전에 호출부에서 어떤 값이 들어갔는지, 그 값이 너무 넓은 타입으로 잡혀 있지는 않은지 확인합니다.
인자 개수가 안 맞는 경우
가장 단순한 원인은 인자 개수 불일치입니다. 함수가 한 개 또는 두 개의 인자를 받도록 선언되어 있는데 세 개를 넘기면, TypeScript는 세 개짜리 overload 후보를 찾을 수 없습니다. 이때는 타입 변환보다 함수 사용 방식부터 확인해야 합니다.
function getProductLabel(name: string): string;
function getProductLabel(name: string, price: number): string;
function getProductLabel(name: string, price?: number) {
return price ? `${name} / ${price}원` : name;
}
getProductLabel("키보드", 39000, "sale");
이 함수는 한 개 또는 두 개의 인자만 받습니다. 그런데 호출부에서 세 번째 인자인 "sale"을 넘겼습니다. TypeScript 입장에서는 이 호출을 받아 줄 함수 형태가 없습니다.
이 상황에서 무작정 overload를 하나 더 추가하면 함수 책임이 흐려질 수 있습니다. 세 번째 값이 정말 함수에 필요한 값인지, 옵션으로 묶어야 하는 값인지, 호출부에서 제거해야 하는 값인지 먼저 나눠야 합니다.
type ProductLabelOption = {
badge?: "sale" | "new";
};
function getProductLabel(name: string, price?: number, option?: ProductLabelOption) {
const label = price ? `${name} / ${price}원` : name;
return option?.badge ? `${label} (${option.badge})` : label;
}
getProductLabel("키보드", 39000, { badge: "sale" });
옵션이 늘어나는 함수라면 세 번째, 네 번째 인자를 계속 붙이는 방식보다 객체로 묶는 쪽이 읽기 쉽습니다. 호출부에서도 { badge: "sale" }처럼 값의 의미가 드러납니다.
인자 타입이 안 맞는 경우
프론트엔드 작업에서는 인자 타입 불일치가 더 자주 나옵니다. 화면에서 받은 값은 문자열인데 함수는 숫자를 기대하거나, API 응답 값에는 null이 섞여 있는데 함수는 항상 문자열만 받는다고 선언된 경우입니다.
const products = [
{ id: 1, name: "키보드" },
{ id: 2, name: "마우스" },
];
function findProductById(id: number) {
return products.find((product) => product.id === id);
}
const selectedId = "2";
findProductById(selectedId);
브라우저의 input, select, URL query string에서 받은 값은 숫자처럼 보여도 대개 문자열입니다. 화면에는 2로 보이지만 TypeScript는 "2"를 string으로 봅니다.
이때 함수 타입을 억지로 넓히기보다, 프로젝트 안에서 상품 ID를 어떤 타입으로 관리할지 정해야 합니다. 내부 로직에서 숫자로 비교한다면 호출 전에 변환하고, 변환 실패도 함께 처리해야 합니다.
const selectedId = "2";
const productId = Number(selectedId);
if (!Number.isNaN(productId)) {
findProductById(productId);
}
반대로 ID를 문자열로 관리하는 프로젝트라면 findProductById의 파라미터를 string으로 바꾸는 것이 맞습니다. 오류를 없애려고 타입을 바꾸는 것이 아니라, 데이터가 이동하는 기준에 함수 타입을 맞춰야 합니다.
union 타입을 그대로 넘길 때 생기는 문제
No overload matches this call에서 많이 막히는 지점은 union 타입입니다. string | number는 문자열도 될 수 있고 숫자도 될 수 있습니다. 사람 눈에는 문자열 overload나 숫자 overload 중 하나에 들어갈 것처럼 보입니다.
하지만 TypeScript는 호출 시점에 어느 타입인지 확정할 수 있어야 합니다. 값이 둘 중 하나일 수 있다는 정보만으로는 특정 overload를 선택하기 어렵습니다. 여기서 타입 좁히기가 필요합니다.
function formatFilterValue(value: string): string;
function formatFilterValue(value: number): string;
function formatFilterValue(value: string | number) {
return String(value).trim();
}
function applyFilter(value: string | number) {
return formatFilterValue(value);
}
이 코드는 구현부만 보면 가능해 보입니다. 그러나 applyFilter 안에서 value는 여전히 string | number입니다. TypeScript는 formatFilterValue의 문자열 버전을 호출해야 하는지, 숫자 버전을 호출해야 하는지 확정하지 못합니다.
function applyFilter(value: string | number) {
if (typeof value === "string") {
return formatFilterValue(value);
}
return formatFilterValue(value);
}
typeof로 분기하면 첫 번째 블록 안에서 value는 string으로 좁혀집니다. 그 아래에서는 number로 좁혀집니다. 이제 TypeScript가 각 호출에 맞는 overload를 선택할 수 있습니다.
관리자 목록 필터처럼 검색어, 카테고리 ID, 정렬 값이 같은 함수로 모이면 타입이 쉽게 넓어집니다. 검색어는 문자열이고 카테고리 ID는 숫자인데 하나의 필터 값으로 묶으면 string | number가 여러 함수에 퍼질 수 있습니다. 그때는 필터 값을 받는 지점에서 분기할지, 애초에 필터 타입을 분리할지 결정해야 합니다.
as any로 덮기 전에 확인할 해결 방법
as any를 붙이면 오류가 사라질 때가 있습니다. 하지만 이 방식은 TypeScript에게 해당 값을 검사하지 말라고 넘기는 것에 가깝습니다. 당장 빌드는 통과할 수 있어도 잘못된 값이 들어왔을 때 추적할 단서가 줄어듭니다.
수정 방향은 세 가지로 나눠 볼 수 있습니다. 호출 전에 타입을 좁힐 수 있는지, 함수가 실제로 여러 타입을 받아야 하는지, 아니면 overload 선언이 현재 사용 방식과 어긋난 상태인지 확인합니다.
호출 전에 타입을 좁히는 방식
type SearchValue = string | number | null;
function normalizeKeyword(keyword: string) {
return keyword.trim().toLowerCase();
}
function handleSearch(value: SearchValue) {
if (typeof value !== "string") {
return "";
}
return normalizeKeyword(value);
}
함수가 문자열만 받아야 한다면 호출부에서 문자열인지 확인하는 방식이 맞습니다. null이나 숫자가 들어왔을 때의 처리도 함께 정리되기 때문에 이후 로직이 덜 흔들립니다.
함수 파라미터 타입을 넓히는 방식
function normalizeKeyword(value: string | number) {
return String(value).trim().toLowerCase();
}
함수가 실제로 문자열과 숫자를 모두 받아도 된다면 파라미터 타입을 union으로 선언할 수 있습니다. 다만 이 경우 함수 내부에서 두 타입을 모두 처리할 책임이 생깁니다. 단순히 오류를 피하려고 타입을 넓히면 예외 처리가 함수 안으로 몰립니다.
overload보다 union이 나은 경우
function formatValue(value: string | number) {
return String(value);
}
입력 타입에 따라 반환 타입이 달라지지 않는다면 overload를 굳이 나눌 필요가 없습니다. 문자열을 넣든 숫자를 넣든 항상 문자열을 반환한다면 union 파라미터 하나로 충분합니다. overload는 호출 형태에 따라 반환 타입이나 동작 기준이 분명히 달라질 때 더 적합합니다.
DOM·라이브러리 함수에서 같은 오류가 날 때 보는 순서

이 오류는 직접 만든 함수보다 DOM API나 라이브러리 함수를 사용할 때 더 당황스럽게 느껴집니다. 에러 위치가 타입 선언 파일까지 이어지면 내 코드가 틀린 건지, 라이브러리 타입이 어려운 건지 구분하기 어렵습니다.
그래도 순서는 크게 다르지 않습니다. 내가 호출한 함수에 넘긴 값부터 봅니다. 이벤트 이름과 콜백의 이벤트 타입이 맞는지, 옵션 객체의 필드 이름이 맞는지, 콜백 함수의 반환값이 라이브러리가 기대하는 형태와 맞는지 확인합니다.
window.addEventListener("click", (event: KeyboardEvent) => {
console.log(event.key);
});
click 이벤트에서 콜백 인자를 KeyboardEvent로 고정하면 타입이 어긋납니다. 클릭 이벤트는 키보드 이벤트가 아니라 마우스 이벤트 계열로 다뤄야 합니다. 이 경우 TypeScript는 addEventListener의 여러 overload 중 현재 콜백과 맞는 후보를 찾지 못할 수 있습니다.
window.addEventListener("click", (event) => {
console.log(event.clientX);
});
콜백 인자 타입을 직접 좁게 적지 않으면 TypeScript가 이벤트 이름을 보고 알맞은 타입을 추론합니다. 꼭 직접 타입을 적어야 한다면 MouseEvent처럼 실제 이벤트와 맞는 타입을 사용해야 합니다.
라이브러리 함수에서도 비슷합니다. 선언 파일을 바로 열기보다 현재 넘긴 옵션 객체, 콜백 함수, 제네릭 지정이 문서의 타입과 같은 모양인지 먼저 비교합니다. 특히 React나 Next.js 프로젝트에서는 이벤트 객체, 상태 값, URL 파라미터, API 응답 값이 섞이면서 타입이 예상보다 넓어지는 일이 많습니다.
정리
No overload matches this call은 함수 호출부를 다시 읽으라는 신호입니다. 함수 이름이 틀렸는지보다 현재 넘긴 인자가 함수가 허용하는 호출 형태 중 하나와 정확히 맞는지 확인해야 합니다.
인자 개수를 보고, 그다음 인자 타입을 봅니다. 값이 string | number, string | null처럼 넓게 잡혀 있다면 호출 전에 타입을 좁힐 수 있는지 확인합니다. 함수가 실제로 여러 타입을 받아야 한다면 파라미터 타입이나 overload 구조를 다시 잡아야 합니다.
as any는 마지막에 남겨둘 선택지입니다. 오류를 숨기는 데는 빠르지만, 함수가 어떤 값을 받을 수 있는지에 대한 기준까지 같이 사라집니다. 다음에 같은 오류를 만나면 오류 메시지 전체를 외우려고 하기보다 함수 후보가 기대한 타입과 내가 넘긴 값의 타입을 나란히 비교하는 쪽이 빠릅니다.