코딩테스트 문제를 풀다 보면 "분명히 정답은 알겠는데 시간 초과(TLE) 때문에 미치겠다"는 순간이 온다. 특히 배열이나 리스트 같은 선형 자료구조에서 데이터를 뒤지는 상황이 그렇다. 우리는 보통 이럴 때 본능적으로 이중 for문을 돌리려 한다. 하지만 데이터가 10만 개, 100만 개로 늘어나는 순간 서버는 비명을 지르고 당신의 합격 통보서는 멀어진다. 이때 필요한 게 바로 '투 포인터(Two Pointers)'라는 기술이다. 겉보기엔 단순해 보이지만, 실무에서 대용량 트래픽 로그를 분석하거나 실시간 데이터 스트림을 처리할 때 이 개념 하나로 인프라 비용을 수천만 원 아낄 수도 있다.
왜 굳이 두 개의 포인터를 써야 하는가?
우리가 흔히 범하는 실수는 "모든 경우의 수를 다 확인해야 직성이 풀린다"는 강박이다. $O(N^2)$의 시간 복잡도는 N=10^5만 되어도 100억 번의 연산을 수행한다. 2026년 현재 최신 CPU 성능이 아무리 좋아졌어도 1초 안에 이걸 처리하는 건 무리다. 투 포인터는 말 그대로 인덱스를 가리키는 포인터 두 개를 조절하면서 $O(N)$으로 문제를 끝내는 기법이다. 대형마트 오픈런 상황을 생각해보자. 입구와 출구 양쪽에서 인원을 체크하며 특정 조건(예: 매장 내 인원 50명 유지)을 맞추는 것과 같다. 굳이 매번 처음부터 끝까지 사람을 다 셀 필요가 없다는 뜻이다.

패턴 1: 양 끝에서 중앙으로 좁혀오기 (Sorted Array)
주로 정렬된 배열에서 특정 합(Target Sum)을 찾을 때 사용한다. 왼쪽 끝(left)과 오른쪽 끝(right)에서 시작해서 합이 크면 right를 줄이고, 합이 작으면 left를 키운다.
- 적용 상황: 두 수의 합, 회문(Palindrome) 검사, 정렬된 상태에서의 중복 제거.
- 이득: 불필요한 탐색 범위를 즉각적으로 소거한다.
- 고통: 데이터가 정렬되어 있지 않다면 정렬 비용(O(N \log N))이 추가된다. 정렬된 상태가 보장되지 않는 실시간 스트리밍 데이터에서는 쓰기 까다롭다.
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) return new int[]{left + 1, right + 1};
if (sum < target) left++;
else right--;
}
return new int[]{-1, -1};
}
실무에서 이거 도입하면 정말 성능이 드라마틱하게 좋아지나요?
질문에 답하자면, **"조건만 맞으면 최소 수십 배에서 수백 배까지 빨라진다"**고 단언할 수 있다. 예를 들어, 우리가 백엔드 아키텍처를 설계하면서 분산된 로그 시스템에서 특정 타임스탬프 범위 내의 이벤트를 추출한다고 가정하자. 이중 루프로 로그를 대조하면 서버 CPU 점유율이 순식간에 90%를 넘기며 Circuit Breaker가 터질 것이다. 하지만 타임스탬프로 정렬된 로그에 투 포인터를 적용하면 메모리 사용량은 일정하게 유지하면서 선형적으로 데이터를 긁어낼 수 있다. 이건 운영 난이도의 문제가 아니라 서비스 생존의 문제다.
패턴 2: 같은 방향으로 움직이는 슬라이딩 윈도우
시작점(start)과 끝점(end)이 모두 인덱스 0에서 출발한다. 특정 조건을 만족할 때까지 end를 이동시켜 윈도우를 키우다가, 조건을 벗어나면 start를 당겨서 윈도우를 줄인다.
이건 직접 운영해 보면 체감이 확 됩니다. 특히 연속된 부분 배열의 최대 합이나 특정 길이를 유지해야 하는 가변 윈도우 문제에서 탁월하다. 동영상 스트리밍이나 주식 호가 데이터처럼 연속성이 중요한 도메인에서 "최근 5분간의 평균 거래량" 같은 지표를 산출할 때 쓴다. 매번 5분치 데이터를 새로 계산하는 게 아니라, 새로 들어온 놈 더하고 나가는 놈 빼주는 식이다.
| 특징 | 양 끝 포인터 (Opposite) | 슬라이딩 윈도우 (Same) |
|---|---|---|
| 주요 목적 | 값 비교 및 타겟팅 | 연속된 구간의 상태 관리 |
| 데이터 상태 | 정렬 필수인 경우가 많음 | 정렬 여부보다 연속성이 중요 |
| 메모리 부하 | 매우 낮음 | 윈도우 상태 저장을 위해 추가 공간 필요할 수 있음 |
패턴 3: 고속 포인터와 저속 포인터 (Fast & Slow)
이른바 '토끼와 거북이' 알고리즘이다. 연결 리스트(Linked List)에서 순환(Cycle)이 있는지 체크하거나 중간 지점을 찾을 때 쓴다. 한 포인터는 한 칸씩, 다른 하나는 두 칸씩 움직인다. 만약 순환이 있다면 결국 둘은 만난다.
실무에서는 메모리 누수를 추적하거나 데이터 구조의 무결성을 검증할 때 응용한다. 예를 들어, 복잡하게 얽힌 객체 참조 관계에서 순환 참조가 발생해 가비지 컬렉터(GC)가 일을 못 하고 있다면? 이런 논리로 참조 트리를 탐색해 루프를 찾아낼 수 있다. 물론 요즘 AI 기반 프로파일링 도구들이 잘 나와 있지만, 밑바닥 원리를 모르면 도구가 뱉는 결과값조차 해석 못 하는 '도구의 노예'가 된다.
백엔드 개발자가 마주할 현실적인 트레이드오프
투 포인터가 만능은 아니다. 코드가 직관적이지 않을 수 있다는 게 최대 단점이다. 단순 for문은 신입 사원도 읽지만, 포인터 두 개가 복잡하게 꼬인 코드는 디버깅 난이도를 올린다. "성능은 $O(N)$인데 가독성은 O(N!)"이 되는 불상사가 생길 수 있다. 또한, 멀티 스레드 환경에서 공유 배열에 투 포인터를 적용하려면 동기화 비용이 발생한다. 이때 발생하는 Lock 경합 때문에 오히려 싱글 스레드보다 느려지는 병목 현상이 생길 수 있으니 주의해야 한다.
결국 어떤 선택을 해야 하는가?
주니어 때는 당장 눈앞의 테스트 케이스를 통과하는 데 급급하겠지만, 시니어의 시선은 달라야 한다. 투 포인터를 쓸지 말지는 다음 기준에 따라 결정하라.
- 데이터의 크기가 10^5 이상인가? 무조건 도입하라. $O(N^2)$은 자살 행위다.
- 데이터가 정렬되어 있거나, 정렬 시 이득이 큰가? 양 끝 포인터 패턴을 우선 검토하라.
- 메모리가 극도로 제한된 환경인가? 추가 배열을 만드는 대신 포인터만 써서 해결하는 투 포인터가 정답이다.
- 팀원들의 평균적인 숙련도는 어떠한가? 성능 차이가 미미하다면(데이터가 작다면) 가독성 좋은 라이브러리 함수나 내장 메서드를 쓰는 게 유지보수 측면에서 낫다.
기술은 도구일 뿐이다. 투 포인터라는 망치를 들었다고 모든 문제를 못으로 보지 마라. 하지만 적어도 대못을 박아야 할 때 손가락으로 밀어 넣는 바보짓은 하지 말아야 한다. 오늘 정리한 이 패턴들은 코딩테스트뿐만 아니라 당신이 만들 거대한 시스템의 기초 체력이 될 것이다.
Spring Security 인증 프로세스: 실무에서 마주하는 구조적 한계와 트레이드오프 분석
보안을 프레임워크에 위임해야 하는 이유: 파편화된 인증 로직의 한계Spring Security가 없던 시절, 혹은 이를 제대로 활용하지 못하는 환경에서는 HTTP 인터셉터나 서블릿 필터 곳곳에 직접 작성한
byteandbit.tistory.com
'Back-end & 알고리즘' 카테고리의 다른 글
| 코테용 이분탐색은 다르다! 무한 루프 없는 매개 변수 탐색 실전 템플릿 (0) | 2026.05.22 |
|---|---|
| 코딩테스트 문자열 인덱스 실수(Out of Bounds) 줄이는 4가지 방어적 코드 패턴 (0) | 2026.05.17 |
| Spring Security 인증 프로세스: 실무에서 마주하는 구조적 한계와 트레이드오프 분석 (0) | 2026.05.09 |
| JPA 연관관계 지옥에서 탈출하기: 복잡성을 줄이는 실무 설계 원칙 (0) | 2026.05.08 |
| 코딩테스트 시간 초과 해결하는 이분탐색(Binary Search) 접근 순서와 설계 팁 (0) | 2026.05.07 |