JavaScript 탐색 알고리즘이란? 배열·문자열·객체 검색 방법 정리
JavaScript로 애플리케이션을 만들다 보면 데이터 안에서 원하는 값을 찾아야 하는 상황이 매우 자주 발생합니다. 예를 들어 사용자 목록에서 특정 ID를 가진 사용자를 찾거나, 상품 목록에서 특정 상품이 존재하는지 확인하거나, 문자열 안에 특정 키워드가 포함되어 있는지 검사하거나, 객체 안에 특정 속성이 있는지 확인하는 작업이 모두 탐색에 해당합니다. JavaScript에는 includes(), indexOf(), find(), findIndex(), some(), Object.keys(), Object.values(), Object.entries() 같은 다양한 내장 메서드가 있지만, 이 메서드들이 어떤 방식으로 데이터를 찾는지 이해하면 더 효율적이고 읽기 쉬운 코드를 작성할 수 있습니다.
탐색 알고리즘의 대표적인 예로는 선형 탐색과 이진 탐색이 있습니다. 선형 탐색은 데이터를 앞에서부터 하나씩 확인하며 원하는 값을 찾는 방식입니다. 데이터가 정렬되어 있지 않아도 사용할 수 있고, 구현이 단순해 JavaScript 실무에서도 매우 자연스럽게 활용됩니다. 반면 이진 탐색은 데이터가 이미 정렬되어 있을 때 탐색 범위를 절반씩 줄이며 빠르게 값을 찾는 방식입니다. 데이터가 많을수록 선형 탐색과 이진 탐색의 성능 차이는 커지기 때문에, 탐색 알고리즘을 이해하는 것은 단순한 문법 학습을 넘어 효율적인 데이터 처리 능력을 기르는 데 중요합니다.
1. JavaScript 탐색 알고리즘이란?
JavaScript 탐색 알고리즘이란 배열, 문자열, 객체와 같은 데이터 구조 안에서 원하는 값이나 조건에 맞는 요소를 찾기 위한 절차와 방법을 의미합니다. 예를 들어 배열 안에 특정 숫자가 있는지 확인하는 경우, 가장 단순하게는 앞에서부터 하나씩 확인할 수 있습니다. 이 방식이 선형 탐색입니다. 반대로 데이터가 정렬되어 있다면 중앙값을 기준으로 왼쪽이나 오른쪽 범위를 버리면서 빠르게 찾을 수도 있습니다. 이 방식이 이진 탐색입니다.
JavaScript에서는 탐색을 직접 알고리즘으로 구현하지 않아도 여러 표준 메서드를 통해 쉽게 처리할 수 있습니다. 배열에서는 includes(), indexOf(), find(), findIndex(), some()을 사용할 수 있고, 문자열에서는 includes(), indexOf(), startsWith(), endsWith()를 사용할 수 있습니다. 객체에서는 Object.hasOwn(), in 연산자, Object.keys(), Object.values(), Object.entries() 등을 활용할 수 있습니다. 중요한 것은 단순히 메서드를 외우는 것이 아니라, 어떤 데이터에서 무엇을 찾는지, 결과로 불리언이 필요한지 요소가 필요한지 인덱스가 필요한지를 구분하는 것입니다.
1.1 탐색은 데이터 처리의 기본이다
탐색은 정렬, 필터링, 집계와 함께 프로그래밍에서 가장 기본적인 데이터 처리 작업입니다. 사용자가 입력한 검색어와 일치하는 항목을 찾거나, 권한 목록 안에 현재 사용자의 권한이 포함되어 있는지 확인하거나, API에서 받은 데이터 중 특정 조건을 만족하는 항목을 찾아 화면에 표시하는 작업은 모두 탐색과 관련됩니다. 작은 데이터에서는 탐색 방식의 차이가 크게 드러나지 않지만, 데이터가 커지고 검색이 반복되면 탐색 알고리즘과 자료구조 선택이 성능에 직접적인 영향을 줄 수 있습니다.
JavaScript의 내장 메서드는 사용하기 쉽지만, 내부적으로는 대개 데이터를 순서대로 확인하는 방식으로 동작하는 경우가 많습니다. 따라서 find()나 includes()를 사용한다고 해서 항상 빠른 것은 아닙니다. 수십 개의 데이터를 다룰 때는 문제가 없지만, 수만 개 이상의 데이터를 반복해서 탐색한다면 Map이나 Set 같은 자료구조로 바꾸는 것이 더 적합할 수 있습니다. 탐색 알고리즘을 이해하면 “지금은 단순한 배열 검색으로 충분한가?”, “반복 검색을 위해 자료구조를 바꿔야 하는가?”를 판단할 수 있습니다.
1.2 탐색 대상에 따라 방법이 달라진다
탐색 대상이 배열인지, 문자열인지, 객체인지에 따라 적합한 방법은 달라집니다. 배열에서는 특정 값이 포함되어 있는지 확인할 수도 있고, 조건에 맞는 객체를 찾을 수도 있으며, 특정 위치의 인덱스가 필요할 수도 있습니다. 문자열에서는 특정 단어나 문자가 포함되어 있는지, 문자열이 특정 접두사로 시작하는지, 특정 패턴과 일치하는지 확인하는 경우가 많습니다. 객체에서는 특정 키가 있는지, 특정 값이 존재하는지, 키와 값의 조합이 조건을 만족하는지를 확인할 수 있습니다.
예를 들어 배열에 값이 존재하는지만 확인하려면 includes()가 가장 명확합니다. 조건에 맞는 객체를 가져와야 한다면 find()가 더 적합합니다. 위치가 필요하다면 indexOf()나 findIndex()를 사용하는 것이 좋습니다. 객체에서 키의 존재 여부를 확인할 때는 Object.hasOwn()이 명확하며, 값 전체를 탐색해야 한다면 Object.values()를 사용할 수 있습니다. 탐색은 단순히 값을 찾는 작업이 아니라, 데이터 구조와 필요한 결과 형태에 맞는 도구를 선택하는 과정입니다.
2. 언제 탐색 알고리즘이 필요한가?
탐색 알고리즘은 데이터 안에서 특정 정보를 찾아야 할 때 필요합니다. 예를 들어 사용자 ID를 기준으로 사용자 정보를 가져오거나, 장바구니에 특정 상품이 있는지 확인하거나, 입력 문자열 안에 금지어가 포함되어 있는지 검사하거나, 설정 객체 안에 특정 옵션이 존재하는지 확인할 때 탐색이 사용됩니다. 대부분의 애플리케이션에서는 이런 작업이 매우 자주 등장하기 때문에, 탐색을 효율적으로 처리하는 방법을 이해하는 것이 중요합니다.
특히 검색이 반복되는 상황에서는 탐색 방식이 성능에 큰 영향을 줄 수 있습니다. 예를 들어 수만 명의 사용자 배열에서 매번 find()로 ID를 찾는다면, 검색이 반복될수록 비용이 커집니다. 이런 경우 처음에 사용자 배열을 Map으로 변환해 ID를 키로 접근하면 훨씬 효율적일 수 있습니다. 탐색 알고리즘을 학습하는 목적은 모든 탐색을 직접 구현하는 것이 아니라, 데이터의 크기와 검색 빈도에 따라 어떤 방식이 적절한지 판단하는 능력을 기르는 데 있습니다.
2.1 조건에 맞는 데이터를 찾는 경우
JavaScript에서는 특정 조건에 맞는 데이터를 찾는 경우가 매우 많습니다. 예를 들어 사용자 목록에서 role이 "admin"인 사용자를 찾거나, 상품 목록에서 가격이 특정 금액 이하인 상품을 찾거나, 게시글 목록에서 특정 slug를 가진 글을 찾는 작업이 있습니다. 이런 경우 배열 메서드인 find()나 filter()가 자주 사용됩니다. find()는 조건을 만족하는 첫 번째 요소를 반환하고, filter()는 조건을 만족하는 모든 요소를 배열로 반환합니다.
따라서 검색 결과로 무엇이 필요한지 먼저 결정해야 합니다. 첫 번째 결과 하나만 필요하다면 find()를 사용하는 것이 자연스럽습니다. 조건에 맞는 모든 항목이 필요하다면 filter()를 사용해야 합니다. 단순히 존재 여부만 알고 싶다면 some()이나 includes()가 더 적합할 수 있습니다. 탐색 메서드를 선택할 때는 “첫 번째 값이 필요한가?”, “모든 값이 필요한가?”, “존재 여부만 필요한가?”를 구분하는 것이 중요합니다.
2.2 검색이 여러 번 반복되는 경우
한 번만 검색한다면 단순한 선형 탐색으로도 충분한 경우가 많습니다. 그러나 같은 데이터에서 여러 번 검색한다면 이야기가 달라집니다. 예를 들어 사용자 배열에서 여러 컴포넌트나 여러 함수가 반복적으로 사용자 ID를 조회한다면, 매번 배열 전체를 확인하는 방식은 비효율적일 수 있습니다. 이때는 데이터를 검색하기 쉬운 형태로 미리 변환하는 것이 좋습니다.
대표적인 방법은 Map이나 Set을 사용하는 것입니다. 특정 ID가 존재하는지만 자주 확인한다면 Set이 유용하고, ID를 기준으로 객체를 가져와야 한다면 Map이 적합합니다. 물론 Map이나 Set을 만드는 데에도 초기 비용이 있습니다. 그러나 이후 검색이 반복된다면 전체 비용을 줄일 수 있습니다. 실무에서는 한 번의 검색 비용만 보지 말고, 전체 흐름에서 검색이 몇 번 발생하는지까지 고려해야 합니다.
3. 배열에서 탐색하기
JavaScript에서 배열은 가장 자주 사용되는 데이터 구조 중 하나이며, 배열 탐색도 매우 자주 발생합니다. 배열 안에 값이 포함되어 있는지 확인하려면 includes()를 사용할 수 있고, 값의 위치가 필요하다면 indexOf()를 사용할 수 있습니다. 조건에 맞는 요소를 가져오고 싶다면 find(), 조건에 맞는 위치가 필요하다면 findIndex(), 조건을 만족하는 요소가 하나라도 있는지만 알고 싶다면 some()을 사용할 수 있습니다.
배열 탐색의 기본은 선형 탐색입니다. 대부분의 배열 탐색 메서드는 앞에서부터 요소를 하나씩 확인하다가 조건을 만족하면 결과를 반환합니다. 목적의 요소가 앞쪽에 있으면 빠르게 끝나지만, 뒤쪽에 있거나 존재하지 않으면 많은 요소를 확인해야 합니다. 데이터가 작을 때는 문제가 되지 않지만, 큰 배열을 자주 탐색한다면 Map, Set, 이진 탐색 또는 서버 측 검색을 고려할 필요가 있습니다.
3.1 값의 존재 여부 확인
배열 안에 특정 값이 있는지만 확인하고 싶다면 includes()가 가장 읽기 쉽습니다.
const roles = ["admin", "editor", "viewer"];console.log(roles.includes("admin")); // trueconsole.log(roles.includes("guest")); // false
includes()는 결과로 true 또는 false를 반환하므로 조건문에서 사용하기 좋습니다. 위치가 필요하다면 indexOf()를 사용할 수 있지만, 존재 여부만 필요하다면 includes()가 의도를 더 명확하게 표현합니다. 단, 객체 배열에서는 객체 참조가 같아야 일치하므로 단순 includes()로 조건 검색을 처리하기 어렵습니다. 이 경우에는 find()나 findIndex()를 사용하는 것이 더 적합합니다.
3.2 조건에 맞는 객체 찾기
객체 배열에서 조건에 맞는 요소를 찾으려면 find()가 유용합니다.
const users = [ { id: 1, name: "Sato" }, { id: 2, name: "Tanaka" }];const user = users.find(item => item.id === 2);console.log(user); // { id: 2, name: "Tanaka" }
find()는 조건을 만족하는 첫 번째 요소를 찾으면 즉시 반환합니다. 따라서 하나의 결과만 필요할 때 효율적입니다. 조건을 만족하는 모든 요소가 필요하다면 filter()를 사용해야 합니다. 실무에서는 이 차이를 잘못 사용하면 불필요하게 전체 배열을 순회하거나, 필요한 결과를 일부만 가져오는 문제가 생길 수 있습니다.
4. 문자열에서 탐색하기
문자열 탐색은 특정 문자, 단어, 구문, 패턴이 텍스트 안에 포함되어 있는지 확인할 때 사용됩니다. JavaScript에서는 includes(), indexOf(), startsWith(), endsWith(), match(), search() 등을 사용할 수 있습니다. 단순히 포함 여부만 확인하고 싶다면 includes()가 가장 명확하고, 위치가 필요하다면 indexOf()가 적합합니다. 정규 표현식을 활용한 복잡한 검색이 필요하다면 match()나 search()를 사용할 수 있습니다.
문자열 탐색은 검색창, 필터링, 입력값 검증, URL 확인, 로그 분석, 키워드 감지 등에서 자주 사용됩니다. 다만 문자열 검색에서는 대소문자 구분, 공백, 전각·반각, 언어별 표기 차이 등을 고려해야 할 수 있습니다. 예를 들어 "JavaScript"와 "javascript"는 기본적으로 다른 문자열로 처리됩니다. 실무에서는 검색 전에 소문자 변환이나 정규화를 적용해 더 안정적인 검색 결과를 만드는 경우가 많습니다.
4.1 includes와 indexOf 사용하기
문자열에 특정 키워드가 포함되어 있는지 확인하려면 includes()를 사용할 수 있습니다.
const title = "JavaScript Search Algorithms";console.log(title.includes("Search")); // trueconsole.log(title.indexOf("Search")); // 11
includes()는 포함 여부만 반환하기 때문에 읽기 쉽습니다. 반면 indexOf()는 키워드가 시작되는 위치를 반환하며, 찾지 못하면 -1을 반환합니다. 따라서 위치가 필요하면 indexOf(), 단순 존재 여부가 필요하면 includes()를 사용하는 것이 좋습니다.
4.2 대소문자를 무시한 검색
영문 문자열에서는 대소문자 차이로 검색 결과가 달라질 수 있습니다. 이를 무시하려면 검색 대상과 검색어를 모두 소문자로 변환한 뒤 비교합니다.
const text = "Learning JavaScript Algorithms";const keyword = "javascript";console.log(text.toLowerCase().includes(keyword.toLowerCase())); // true
다만 많은 데이터를 반복 검색할 때마다 toLowerCase()를 호출하면 비용이 커질 수 있습니다. 이런 경우 검색용 필드를 미리 만들어 두거나, 데이터 로딩 시점에 정규화된 문자열을 준비하는 방식이 더 효율적입니다.
5. 객체에서 탐색하기
JavaScript 객체를 탐색할 때는 무엇을 찾는지에 따라 방법이 달라집니다. 특정 키가 존재하는지 확인하려면 Object.hasOwn()이나 in 연산자를 사용할 수 있습니다. 값 목록에서 특정 값을 찾으려면 Object.values()를 사용할 수 있고, 키와 값을 함께 확인하려면 Object.entries()가 유용합니다. 객체는 배열처럼 순차적인 데이터라기보다 키와 값의 대응 관계를 가진 구조이므로, 키를 알고 있다면 직접 접근하는 것이 가장 간단하고 빠릅니다.
예를 들어 user["name"]처럼 키를 알고 있다면 바로 값을 가져올 수 있습니다. 그러나 값으로 키를 찾거나, 조건에 맞는 키와 값의 조합을 찾아야 한다면 객체를 배열 형태로 변환해 탐색해야 할 수 있습니다. 이 방식은 편리하지만, 매번 배열을 새로 만들기 때문에 데이터가 크거나 반복 탐색이 많으면 비용이 커질 수 있습니다. 이런 경우에는 Map 자료구조를 사용하는 것도 좋은 선택입니다.
5.1 키 존재 여부 확인
객체에 특정 키가 있는지 확인하려면 다음과 같이 작성할 수 있습니다.
const user = { id: 1, name: "Sato", role: "admin"};console.log("role" in user); // trueconsole.log(Object.hasOwn(user, "role")); // true
in 연산자는 프로토타입 체인에 있는 속성까지 확인합니다. 반면 Object.hasOwn()은 객체 자신이 직접 가진 속성만 확인합니다. API 응답이나 설정 객체처럼 실제 데이터 필드의 존재 여부를 확인할 때는 Object.hasOwn()이 더 명확한 경우가 많습니다.
5.2 값 또는 엔트리 기준으로 탐색하기
값이나 키-값 조합을 기준으로 탐색하려면 Object.values()나 Object.entries()를 사용할 수 있습니다.
const settings = { theme: "dark", language: "ko", layout: "grid"};const result = Object.entries(settings).find(([key, value]) => { return key === "language" && value === "ko";});console.log(result); // ["language", "ko"]
이 방식은 작은 객체에서는 매우 편리합니다. 하지만 큰 객체를 자주 탐색한다면 매번 엔트리 배열을 만드는 비용을 고려해야 합니다. 검색이 반복되는 구조라면 처음부터 Map으로 데이터를 관리하는 편이 더 적합할 수 있습니다.
6. 선형 탐색
선형 탐색은 데이터를 앞에서부터 하나씩 확인하여 원하는 값이나 조건에 맞는 요소를 찾는 가장 기본적인 탐색 알고리즘입니다. 영어로는 Linear Search라고 하며, 한국어로는 선형 탐색 또는 순차 탐색이라고 부릅니다. 데이터가 정렬되어 있을 필요가 없고, 구현이 매우 단순하기 때문에 알고리즘 학습의 첫 단계로 자주 등장합니다. JavaScript의 find(), findIndex(), includes()도 개념적으로는 선형 탐색과 가까운 방식으로 이해할 수 있습니다.
선형 탐색의 시간 복잡도는 O(n)입니다. 가장 좋은 경우에는 첫 번째 요소에서 바로 찾을 수 있지만, 가장 나쁜 경우에는 마지막 요소까지 확인하거나 전체를 확인해도 찾지 못할 수 있습니다. 따라서 작은 데이터나 한 번만 검색하는 작업에는 충분히 적합하지만, 큰 데이터를 반복적으로 검색하는 경우에는 더 효율적인 방법을 고려해야 합니다.
6.1 선형 탐색 기본 코드
function linearSearch(array, target) { for (let i = 0; i < array.length; i++) { if (array[i] === target) { return i; } } return -1;}console.log(linearSearch([10, 20, 30], 20)); // 1
이 함수는 목표 값을 찾으면 해당 인덱스를 반환하고, 찾지 못하면 -1을 반환합니다. 코드가 단순하기 때문에 선형 탐색의 동작을 이해하기에 좋습니다.
6.2 선형 탐색이 적합한 경우
선형 탐색은 데이터가 정렬되어 있지 않거나, 데이터 수가 적거나, 조건이 복잡할 때 적합합니다. 예를 들어 여러 조건을 만족하는 객체를 찾는 경우, 이진 탐색보다 find()를 사용하는 선형 탐색 방식이 훨씬 자연스럽습니다.
반면 같은 배열에서 수천 번 이상 반복해서 검색해야 한다면 선형 탐색은 비효율적일 수 있습니다. 이때는 Map이나 Set 같은 자료구조를 활용해 검색 비용을 줄이는 것이 좋습니다.
7. 이진 탐색
이진 탐색은 정렬된 데이터에서 탐색 범위를 절반씩 줄이며 원하는 값을 찾는 알고리즘입니다. 영어로는 Binary Search라고 하며, 한국어로는 이진 탐색이라고 합니다. 오름차순으로 정렬된 배열에서 중앙값을 확인하고, 찾는 값이 중앙값보다 작으면 왼쪽 범위만 탐색하고, 크면 오른쪽 범위만 탐색합니다. 이 과정을 반복하면 매우 적은 비교 횟수로 값을 찾을 수 있습니다.
이진 탐색의 시간 복잡도는 O(log n)입니다. 데이터 수가 늘어나도 탐색 횟수가 매우 천천히 증가하기 때문에, 큰 정렬 데이터에서 강력한 성능을 발휘합니다. 하지만 반드시 데이터가 정렬되어 있어야 하며, 정렬 기준과 탐색 기준이 일치해야 합니다. 정렬되지 않은 배열에 이진 탐색을 사용하면 잘못된 결과를 얻을 수 있습니다.
7.1 이진 탐색 기본 코드
function binarySearch(array, target) { let left = 0; let right = array.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (array[mid] === target) { return mid; } if (array[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1;}console.log(binarySearch([1, 3, 5, 8, 10], 8)); // 3
이 코드는 오름차순 배열을 전제로 합니다. left, right, mid를 사용해 현재 탐색 범위를 관리하고, 매 반복마다 범위를 줄입니다.
7.2 이진 탐색이 적합한 경우
이진 탐색은 데이터가 이미 정렬되어 있고, 데이터 수가 많으며, 검색이 여러 번 반복될 때 적합합니다. 예를 들어 정렬된 ID 목록, 시간순 데이터, 점수순 데이터에서 특정 값을 자주 찾는 경우 사용할 수 있습니다.
다만 한 번 검색하기 위해 정렬되지 않은 배열을 먼저 정렬해야 한다면, 정렬 비용까지 고려해야 합니다. 검색이 한 번뿐이라면 단순 선형 탐색이 더 현실적일 수 있습니다.
8. 탐색 방법 비교
탐색 방법을 선택할 때는 데이터 크기, 정렬 여부, 검색 횟수, 필요한 결과 형태를 함께 고려해야 합니다. 작은 배열에서 한 번만 검색한다면 includes()나 find() 같은 표준 메서드가 가장 적합합니다. 정렬된 큰 데이터를 반복해서 검색한다면 이진 탐색이 유용할 수 있습니다. 키 기반으로 값을 자주 가져와야 한다면 배열보다 Map을 사용하는 것이 더 적절할 수 있습니다.
실무에서는 알고리즘을 직접 구현하기보다 표준 메서드와 자료구조를 적절히 사용하는 것이 중요합니다. 하지만 선형 탐색과 이진 탐색의 차이를 알고 있으면, 특정 코드가 왜 느려지는지, 어떤 자료구조로 바꾸면 좋은지 판단하기 쉬워집니다.
8.1 선형 탐색과 이진 탐색의 차이
선형 탐색은 정렬되지 않은 데이터에서도 사용할 수 있고, 구현이 단순합니다. 반면 시간 복잡도는 O(n)이므로 데이터가 많아질수록 느려질 수 있습니다. 이진 탐색은 정렬된 데이터에서만 사용할 수 있지만, 시간 복잡도는 O(log n)이므로 큰 데이터에서 매우 빠릅니다.
따라서 작은 데이터나 복잡한 조건 검색에는 선형 탐색이 적합하고, 정렬된 대량 데이터의 반복 검색에는 이진 탐색이 적합합니다.
8.2 Map과 Set 활용
존재 여부를 자주 확인해야 한다면 Set을 사용할 수 있습니다.
const allowedIds = new Set([1, 2, 3, 5, 8]);console.log(allowedIds.has(5)); // trueconsole.log(allowedIds.has(10)); // false
키를 기준으로 값을 자주 가져와야 한다면 Map이 적합합니다. Map과 Set은 처음 만드는 비용이 있지만, 반복 검색이 많을 때 전체 성능을 개선할 수 있습니다.
마치며
JavaScript의 탐색 알고리즘은 배열, 문자열, 객체 같은 데이터에서 원하는 값을 찾기 위한 기본 개념입니다. 선형 탐색은 단순하고 유연하며 정렬되지 않은 데이터에도 사용할 수 있습니다. 이진 탐색은 정렬된 데이터에서 탐색 범위를 절반씩 줄이기 때문에 대량 데이터에서 매우 효율적입니다.
실무에서는 모든 탐색 알고리즘을 직접 구현할 필요는 없습니다. 대부분의 경우 includes(), find(), findIndex(), filter(), Object.hasOwn(), Map, Set 같은 표준 기능을 적절히 사용하는 것이 중요합니다. 그러나 그 내부 개념을 이해하고 있으면 데이터가 많아졌을 때 어떤 방식이 병목이 될 수 있는지 판단할 수 있습니다.
탐색 방법을 선택할 때는 무엇을 찾는지, 데이터가 어떤 구조인지, 정렬되어 있는지, 검색이 몇 번 반복되는지를 함께 고려해야 합니다. 이러한 기준을 바탕으로 선형 탐색, 이진 탐색, 표준 메서드, Map, Set을 적절히 선택하면 읽기 쉽고 효율적인 JavaScript 코드를 작성할 수 있습니다.。
EN
JP
KR