뒤로가기
프론트엔드 코딩 테스트, 이렇게 준비했다

October 17, 2021

coding-testcareerfrontendinterview

작년 여름, 이직을 결심하고 프로그래머스를 열었다. 레벨 2 문제 하나를 골라 풀기 시작했는데, 30분이 지나도 for 루프 하나 제대로 못 짰다. 매일 React 코드를 수백 줄씩 쓰는 사람이, 배열에서 조건에 맞는 값을 찾는 문제 앞에서 멍하니 앉아 있었다.

현실 자각이 빨리 온 건 다행이었다. 프론트엔드 개발자라고 해서 프론트엔드만 시키는 회사는 거의 없다. 토스, 카카오, 네이버, 라인 — 이런 회사들은 전부 코딩 테스트를 본다. 그것도 알고리즘만이 아니라, 과제형 테스트에 라이브 코딩까지 조합해서.

3개월 동안 준비했다. 그 과정에서 효과 있었던 것, 완전히 시간 낭비였던 것이 꽤 뚜렷하게 갈렸다.

프론트엔드 코딩 테스트의 세 가지 유형#

먼저 지형도부터 파악하자. 내가 지원한 6개 회사의 전형을 정리해 보면 이런 패턴이었다.

알고리즘 테스트: 프로그래머스나 자체 플랫폼에서 2~4문제, 제한 시간 2시간 내외. 카카오, 네이버가 대표적이다. 보통 1차 필터 역할을 한다.

과제형 테스트: "이 API를 사용해서 검색 기능이 있는 고양이 사진 갤러리를 만드세요" 같은 과제를 3일~1주일 안에 제출. 토스 프론트엔드 챌린지가 유명하고, 중소 스타트업에서도 많이 쓴다.

라이브 코딩: 면접관 앞에서 실시간으로 코드를 짠다. 화면 공유를 켜고, 문제를 받고, 풀어 나간다. 보통 30분~1시간. 이게 제일 긴장된다.

회사마다 조합이 다르다. 카카오는 알고리즘 → 면접이고, 어떤 스타트업은 과제형만 보기도 한다. 내가 지원한 한 곳은 알고리즘 + 과제형 + 라이브 코딩을 전부 다 봤다. 각각 평가하는 게 다르기 때문에 준비 방법도 달라야 한다.

알고리즘: 프론트엔드에 필요한 범위는 생각보다 좁다#

이걸 일찍 깨달았으면 한 달은 아꼈을 거다.

처음에 나는 "코딩 테스트 준비" 하면 떠오르는 그 루트를 탔다. 백준에서 DP 문제를 풀고, 세그먼트 트리를 공부하고, 위상 정렬을 보고. 한 달을 그렇게 썼다. 결과? 실제 시험에서 DP가 나온 적은 딱 한 번, 그것도 점화식이 단순한 레벨이었다. 세그먼트 트리는 당연히 안 나왔다.

프론트엔드 코딩 테스트에서 실제로 자주 나오는 주제는 이렇다:

  • 문자열 처리: 파싱, 변환, 정규표현식. 프론트엔드가 결국 사용자 입력을 다루는 일이라 그런지 체감상 가장 자주 나왔다.
  • 배열/객체 조작: 정렬, 필터링, 그룹핑, 중복 제거. JavaScript의 Array.prototype 메서드를 자유자재로 쓸 수 있는지.
  • BFS/DFS: 그래프 탐색보다는 트리 구조 탐색이 많이 나온다. DOM 트리를 생각하면 왜 나오는지 이해가 된다.
  • 구현: 말 그대로 "요구사항을 정확히 구현할 수 있느냐". 시뮬레이션 문제에 가까운 것들.

반면에 시간 낭비였던 것들:

  • 고난도 DP (배낭 문제, LIS 변형 등)
  • 고급 그래프 알고리즘 (다익스트라, 플로이드-워셜)
  • 복잡한 자료구조 (세그먼트 트리, 트라이)

물론 네이버나 카카오 3번, 4번 문제에서 가끔 나온다. 하지만 프론트엔드 지원자가 그 문제까지 풀어야 통과하는 경우는 드물다. 12번을 빠르고 정확하게 푸는 게 34번을 끙끙대다 시간 날리는 것보다 훨씬 낫다.

내가 실제로 따른 알고리즘 학습 경로#

프로그래머스 레벨 1에서 시작했다. 자존심 상했지만 필요한 과정이었다. JavaScript 내장 메서드의 감을 되찾는 시간이라고 생각했다.

javascript
// 프로그래머스 "같은 숫자는 싫어" - 레벨 1
// 이런 문제부터 시작했다
function solution(arr) {
  return arr.filter((val, idx) => val !== arr[idx + 1]);
}

이렇게 간단한 문제도 처음엔 for 루프로 풀려고 했다. filter 한 줄이면 되는 걸. 실무에서 매일 쓰는 메서드인데도 "알고리즘 문제"라는 프레임이 씌워지니까 갑자기 기본부터 흔들렸다. 이런 감각을 되찾는 데 1주일 정도 걸렸다.

레벨 1을 20문제 정도 풀고 나서 레벨 2로 올렸다. 여기가 핵심 구간이다. 프론트엔드 코딩 테스트의 1~2번 문제가 대략 이 난이도에 해당한다.

javascript
// 프로그래머스 "기능개발" - 레벨 2
// 큐 개념을 활용하는 문제. 이런 류가 실제 시험에도 잘 나온다.
function solution(progresses, speeds) {
  const answer = [];
  const days = progresses.map((p, i) => Math.ceil((100 - p) / speeds[i]));

  let maxDay = days[0];
  let count = 1;

  for (let i = 1; i < days.length; i++) {
    if (days[i] <= maxDay) {
      count++;
    } else {
      answer.push(count);
      maxDay = days[i];
      count = 1;
    }
  }
  answer.push(count);
  return answer;
}

레벨 2를 약 50문제 풀었다. 그 다음 백준 골드 하위(골드 5~4)로 넘어갔다. 여기서 BFS/DFS와 구현 문제를 집중적으로 풀었다. 이 단계에서 효과적이었던 건 문제를 유형별로 묶어서 푸는 것이었다. BFS를 일주일 동안 10문제, 그다음 주에 문자열 10문제. 이렇게 하면 패턴이 몸에 배인다.

하루에 몇 문제를 풀었냐고? 평일에는 출근 전 1문제, 주말에는 3~4문제. 이 페이스를 2개월 유지했다. 총 100문제 좀 넘게 풀었다. "하루 5문제씩 풀었다"는 후기들을 보면 대단하다고 느끼겠지만, 회사 다니면서 그 페이스를 유지하는 건 현실적으로 불가능했다.

과제형 테스트: 여기서 진짜 차이가 난다#

알고리즘이 "통과/탈락" 필터라면, 과제형은 "이 사람이 실제로 어떻게 일하는지"를 보는 테스트다.

내가 받았던 과제 중 하나는 이랬다. 공개 API를 사용해서 이미지 검색 앱을 만들어라. 기능 요구사항은 검색, 페이지네이션, 상세 보기. 기한은 5일.

기능 자체는 어렵지 않다. 솔직히 하루면 "동작하는" 수준으로는 만들 수 있다. 하지만 과제형에서 평가하는 건 "동작하느냐"가 아니다. 어떤 구조로 짰는지, 에러는 어떻게 처리했는지, 코드를 읽는 사람을 얼마나 배려했는지를 본다.

폴더 구조#

과제를 제출하고 나서 면접에서 들은 피드백 중 기억나는 게 있다. "폴더 구조를 보면 이 사람이 어떤 규모의 프로젝트를 해봤는지 감이 온다"는 말이었다.

text
src/
├── components/
│   ├── common/
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   └── ErrorFallback.tsx
│   └── search/
│       ├── SearchBar.tsx
│       ├── SearchResults.tsx
│       └── ImageCard.tsx
├── hooks/
│   ├── useDebounce.ts
│   └── useIntersectionObserver.ts
├── api/
│   ├── client.ts
│   └── imageApi.ts
├── types/
│   └── image.ts
└── utils/
    └── formatDate.ts

거창한 구조가 아니다. 하지만 컴포넌트가 역할별로 분리되어 있고, API 호출 로직이 컴포넌트 밖에 있고, 커스텀 훅이 재사용 가능한 형태로 빠져 있다. 이것만으로도 "이 사람은 관심사 분리를 안다"는 인상을 준다.

에러 핸들링#

과제형에서 제일 차별화되는 포인트가 에러 핸들링이라고 나는 확신한다. 대부분의 지원자가 happy path만 구현한다. 검색이 잘 되는 경우, API가 정상 응답하는 경우, 네트워크가 빠른 경우. 그런데 현실은 그렇지 않다.

tsx
// 이런 코드를 많이 본다
const { data } = await fetch(url);
return data.results.map(item => <ImageCard key={item.id} {...item} />);
tsx
// 과제에서 이렇게 짜면 눈에 띈다
function SearchResults({ query }: { query: string }) {
  const { data, error, isLoading } = useImageSearch(query);

  if (isLoading) return <SearchSkeleton />;
  if (error) return <ErrorFallback error={error} onRetry={()=> refetch()} />;
  if (data.results.length === 0) return <EmptyState query={query} />;

  return (
    <div className="grid grid-cols-3 gap-4">
      {data.results.map(image => (
        <ImageCard key={image.id} image={image} />
      ))}
    </div>
  );
}

로딩 상태, 에러 상태, 빈 결과 상태. 이 세 가지를 처리하는 것만으로도 과제의 수준이 확 올라간다. 거기에 retry 버튼까지 달면 "이 사람은 프로덕션을 경험해봤구나"라는 느낌을 준다.

README의 힘#

과제를 제출할 때 README를 대충 쓰거나 아예 안 쓰는 사람이 많다. 나도 처음엔 그랬다. "코드를 보면 알 텐데 뭘 굳이."

동료 프론트엔드 개발자 민지가 이직할 때 과제형 피드백을 공유해 줬는데, 면접관이 README를 먼저 읽었다고 했다. 코드를 실행하기도 전에. 그 말을 듣고 README에 공을 들이기 시작했다.

내가 쓴 README에 포함한 것들:

  • 실행 방법 (clone → install → dev server 실행까지 복붙 가능하게)
  • 기술 선택 이유 ("React Query를 쓴 이유: 서버 상태 캐싱과 자동 refetch가 필요했기 때문")
  • 고민했던 지점 ("페이지네이션을 offset 방식 대신 cursor 방식으로 선택한 이유")
  • 개선하고 싶은 부분 ("시간이 더 있었다면 이미지 lazy loading과 가상 스크롤을 적용했을 것")

마지막 항목이 특히 중요하다. 과제의 한계를 스스로 인식하고 있다는 걸 보여주기 때문이다. 면접에서 이 부분을 가지고 대화가 이어지는 경우가 많았다.

라이브 코딩: 손이 떨린다#

라이브 코딩은 다른 차원의 테스트다.

혼자 방에서 문제를 푸는 것과, 화면 공유를 켜고 면접관이 지켜보는 가운데 코드를 치는 것은 완전히 다른 경험이다. 나는 첫 라이브 코딩에서 Array.prototype.reduce의 초기값을 까먹었다. 매일 쓰는 메서드다. 면접관 두 명이 보고 있으니까 머리가 하얘진 거다.

javascript
// 긴장해서 이렇게 썼다
const sum = arr.reduce((acc, cur) => acc + cur);
// 빈 배열이 들어오면 에러가 난다는 걸 면접관이 물어보고 나서야 깨달았다

// 이렇게 했어야 했다
const sum = arr.reduce((acc, cur) => acc + cur, 0);

이런 사소한 실수가 쌓이면 자신감이 무너진다. 한 번 무너지면 그 다음부터는 쉬운 것도 어렵게 느껴진다.

라이브 코딩에서 평가하는 건 최종 결과물이 아니다. 문제를 풀어나가는 과정을 본다. 어떤 질문을 하는지, 어떻게 접근하는지, 막혔을 때 어떻게 대처하는지.

내가 두 번째 라이브 코딩부터 적용한 방법이 있다.

코드를 치기 전에 말로 먼저 설명한다. "이 문제는 입력을 파싱해서 트리 구조로 만든 다음, DFS로 탐색하면 될 것 같습니다. 먼저 파싱 함수를 만들고, 그다음 탐색 로직을 짜겠습니다." 이렇게 로드맵을 깔아놓으면 중간에 막혀도 면접관이 내 의도를 알고 있으니까 힌트를 주기도 한다.

모르면 모른다고 한다. 한 라이브 코딩에서 정규표현식의 lookahead 문법이 필요한 상황이 있었다. 문법이 정확히 기억 안 났다. "lookahead가 필요한 건 아는데, 정확한 문법이 바로 떠오르지 않습니다. 일단 다른 방식으로 구현하고, 시간이 남으면 정규표현식으로 리팩토링해도 될까요?"라고 말했다. 면접관이 고개를 끄덕였고, 결과적으로 그 회사에서 오퍼를 받았다.

긴장을 관리하는 건 기술적인 준비만큼 중요하다. 나는 라이브 코딩 전에 친구한테 부탁해서 모의 면접을 3번 했다. Discord 화면 공유를 켜고, 친구가 프로그래머스에서 문제를 하나 골라서 불러주고, 내가 풀었다. 처음에는 친구 앞에서도 긴장했는데, 세 번째쯤 되니까 "누군가 보고 있는 상태에서 코드를 치는 것" 자체에 익숙해졌다. 이게 실전에서 꽤 도움이 됐다.

시간 낭비였던 것들#

3개월 준비 기간 중 체감상 1개월은 비효율적으로 썼다. 돌아간다면 안 했을 것들.

LeetCode hard 문제 풀기. 영어 면접을 볼 것도 아니었고, 한국 회사 프론트엔드 코딩 테스트에서 LeetCode hard 수준이 나오는 경우는 거의 없다. 프로그래머스와 백준에 집중하는 게 맞다.

알고리즘 이론서 정독. '알고리즘 문제 해결 전략' 책을 펼쳐놓고 처음부터 읽기 시작했다. 좋은 책이지만 취준 기간에 읽을 책은 아니다. 문제를 풀다가 모르는 개념이 나오면 그때 찾아보는 게 맞다.

완벽한 풀이에 집착하기. 한 문제에 3시간씩 매달렸던 적이 있다. 30분 안에 방향이 안 잡히면 풀이를 보고 이해하는 게 훨씬 효율적이다. 풀이를 본 뒤 다음 날 다시 푸는 게 내 방식이었다.

각 유형별 실질적인 타임라인#

내가 실제로 따른 3개월 스케줄이다.

1개월 차: 프로그래머스 레벨 1~2. 하루 1문제. JavaScript 기본기를 되살리는 시간. 주말에는 과제형 연습으로 간단한 토이 프로젝트를 하나 만들었다.

2개월 차: 프로그래머스 레벨 2 + 백준 골드 하위. BFS/DFS, 구현 문제 집중. 이 시기에 과제형 모의 연습을 2번 했다. 공개 API를 골라서 3일 안에 앱을 완성하는 자체 훈련이었다.

3개월 차: 실전 지원 시작. 알고리즘은 유지 모드로 하루 1문제만. 나머지 시간은 과제형 제출물 다듬기와 라이브 코딩 모의 연습에 썼다.

면접관이 진짜 보는 것#

알고리즘 테스트에서는 "이 사람이 논리적으로 사고하는가"를 본다. 과제형에서는 "이 사람이 실무에서 어떤 코드를 짜는가"를 본다. 라이브 코딩에서는 "이 사람과 같이 일할 수 있는가"를 본다.

세 가지가 전부 다르다. 알고리즘만 잘해서는 안 되고, 코드만 잘 짜서도 안 되고, 커뮤니케이션만 좋아서도 안 된다. 근데 셋 다 완벽할 필요도 없다. 각각에서 "이 사람은 기본은 되는구나"라는 확신을 주면 된다.

나는 알고리즘에서 한 회사를 떨어졌고, 과제형에서 한 회사를 떨어졌고, 최종 면접에서 한 회사를 떨어졌다. 그리고 두 곳에서 오퍼를 받았다. 6전 2승. 이게 현실적인 숫자다.

준비 과정은 길고 지치지만, 한 가지 좋은 점이 있다. 코딩 테스트를 준비하면서 다시 기본기를 잡게 된다. Array.prototype의 메서드들을 다시 한번 훑게 되고, 평소에 안 쓰던 Map이나 Set을 써보게 되고, 시간 복잡도를 의식하면서 코드를 짜는 습관이 생긴다. 그게 실무에도 돌아온다. 내가 준비 기간에 익힌 useDebounce 커스텀 훅은 지금도 프로젝트에서 쓰고 있다.

누군가 "프론트엔드인데 알고리즘을 왜 봐야 하나"라고 물으면, 나는 이렇게 대답한다. 좋아서 하는 건 아니다. 하지만 이 과정을 거치면 분명히 더 나은 개발자가 돼서 나온다. 그리고 그 회사 뱃지를 달고 있으면 다음 커리어에서도 선택지가 넓어진다. 싫어도 해야 하는 게 있고, 해보면 남는 게 있다.