
토스 Frontend Fundamentals 모의고사로 배운 것들

얼마 전 토스에서 흥미로운 행사가 열렸습니다. 실제 채용 과제를 공개하고, 직접 풀어본 뒤 시니어 개발자분들의 라이브 해설까지 들을 수 있는 Frontend Fundamentals 모의고사였죠.
링크드인에서 이 소식을 접하자마자 바로 신청했습니다. 토스가 생각하는 “좋은 코드”를 직접 체험해볼 수 있는 기회라니, 놓칠 수 없었거든요.
모의고사와 해설을 통해 제가 잘못 알고 있던 부분들을 많이 깨달을 수 있었는데요. 이 글을 통해 그 배움을 정리하고 공유해보려고 합니다.
모의고사 진행 방식
과정은 이랬습니다. 금요일 저녁에 과제가 공개되고, 일요일 오후까지 제출해야 했어요. 주제는 적금 계산기 구현이었고, 핵심 요구사항은 명확했습니다.
“서비스의 유지보수나 장기적인 확장성을 고려한 설계, 추상화 관점에 집중해서 기능을 구현해 주세요.”
기능 구현보다 어떻게 설계하느냐에 초점을 맞춘 과제였죠. 2시간이라는 권장 시간 제한도 있었지만, 저는 학습 목적으로 천천히 설계부터 시작했습니다.
해설에서 배운 핵심 인사이트
라이브 해설은 정말 인상 깊었습니다. 토스의 한재엽님이 처음부터 과제를 함께 풀어주셨는데, 매 순간마다의 판단과 이유를 들을 수 있어서 좋았어요. 특히 기억에 남는 내용을 정리해봤습니다.
1. 코드보다 요구사항을 먼저 보자
보통 페이지에 스켈레톤 코드가 있으면 거기서 출발하게 되잖아요. 하지만 요구사항만 봤을 때 내가 생각하는 이상적인 형태가 무엇인지 수도 코드처럼 먼저 작성해보고 시작하는 것이 좋다고 하셨습니다.
세부 구현에 사로잡혀 중요한 것을 놓치지 않기 위해서죠.
2. UI와 코드 구조를 1:1로 매핑하자
화면에 보이는 것과 컴포넌트 계층이 일치해야 유지보수가 쉽습니다. 마치 지도처럼요. 페이지 컴포넌트에서 입력 필드가 몇 개인지, 탭이 어떻게 구성되어 있는지 한눈에 파악할 수 있어야 한다는 거예요.
export function SavingsCalculatorPage() {
return (
<>
<NavigationBar title="적금 계산기" />
<Spacing size={16} />
<AmountField label="목표 금액" />
<AmountField label="월 납입액" />
<Select label="저축 기간" />
<Tab value={selectedTab} onChange={setSelectedTab}>
<Tab.Item value="products">적금 상품</Tab.Item>
<Tab.Item value="results">계산 결과</Tab.Item>
</Tab>
</>
);
}이렇게 작성하면 다른 개발자가 화면을 보고 코드에서 해당 컴포넌트를 바로 찾을 수 있겠죠.
3. 추출이 아닌 추상화를 하자
이게 가장 뼈를 때리는(?) 내용이었습니다. 그동안 저도 “추상화 한다”고 생각하며 분리했던 코드들이 사실은 그냥 추출에 가까웠던 것 같거든요.
상태가 많다고, 컴포넌트가 많다고 분리하는 게 아니라 책임에 맞게 추상화를 해야 한다고 강조하셨습니다. 재사용을 미리 가정하고 분리하는 것보다 책임을 명확히 하면 재사용성은 따라온다는 말씀이 인상적이었어요.
4. useState를 그대로 노출하지 말자
탭 상태 관리를 예로 들어주셨는데요, 저는 보통 이렇게 작성합니다.
const [selectedTab, setSelectedTab] = useState('products');하지만 해설에서는 커스텀 훅으로 래핑하는 방식을 제안하셨어요.
// useView 내부에서 useState를 사용
function useView(initialView: string) {
const [view, setView] = useState(initialView);
return [view, setView] as const;
}
// 사용하는 쪽
const [view, setView] = useView('products');지금은 내부가 단순히 useState지만, 나중에 URL 쿼리 스트링과 동기화하거나 로컬 스토리지에 저장하도록 바꿔도 사용하는 쪽 코드는 전혀 수정할 필요가 없습니다. 세부 구현이 커스텀 훅 안에 캡슐화되어 있으니까요.
“무엇을 하는지”는 명확하게 드러내고, “어떻게 하는지”는 숨긴다. 이게 진짜 추상화라는 걸 다시 한번 깨달았습니다.
5. Props Drilling은 상태 분리 실패의 신호다
Props Drilling을 해결하려고 전역 상태를 도입하는 건 올바른 방법이 아니라고 하셨어요. 대신 컴포넌트 위계 조정으로 해결해야 한다고요. 합성 컴포넌트나 render props 같은 컴포지션 기법을 활용하면 됩니다.
기억에 남는 표현들
해설 중에 짧지만 와닿는 표현들이 많았어요.
- “도메인 로직이 뭐냐? 내가 바꾸면 큰일 나는 거.”
- “옵셔널과 null은 전염된다. 그래서 최소화해야 한다.”
- “input은 input답게.” (도메인 결합을 줄여야 재사용성이 높아진다)
- “지도의 축척을 바꾸듯이.” (추상화 레벨을 조절하라는 비유)
특히 지도 비유가 좋았는데요, 페이지 컴포넌트에 세부 구현이 모두 노출되면 “세계 지도인데 아파트까지 표시되는 느낌”이라고 하셨거든요. 정말 명쾌한 표현이죠.
디스커션에서 얻은 것들
GitHub Discussion에서도 다양한 주제로 토론이 이어졌습니다. 특히 폴더 구조와 상태 관리에 대한 논의가 활발했어요.
FSD, DDD 같은 방법론보다 중요한 건 비슷한 도메인끼리 응집도가 높은가라는 답변이 인상적이었습니다. 폴더 구조는 그다음 문제라는 거죠.
Context API vs Zustand 선택에 대한 논의도 있었는데, 결국 핵심은 “어떤 도구를 쓰느냐”보다 “상태의 범위와 생명주기를 어떻게 설계하느냐”라는 결론이었습니다.
느낀 점
솔직히 말하면, 이번 모의고사는 제가 잘못 생각하고 있던 부분들을 많이 깨닫게 해줬습니다.
- 추상화한다고 분리했던 코드가 사실은 그냥 추출이었다는 것
- 재사용을 미리 가정하고 분리하는 습관이 있었다는 것
- UI와 코드 구조의 1:1 매핑을 크게 신경 쓰지 않았다는 것
기본에 충실한 게 중요하다는 점을 다시 한번 느꼈습니다. 어떤 아키텍처를 적용하느냐보다, 한 줄의 코드를 작성하더라도 얼마나 깊이 고민하느냐가 유지보수성과 가독성에 훨씬 중요하다는 것을요.
성장이 정체되었다고 생각하던 시점에서 좋은 자극을 받았습니다. 앞으로 평소 업무에서부터 좋은 코드를 작성하기 위해 더 많이 고민하는 시간을 가져야겠어요.
마치며
혹시 다음에도 이런 기회가 생긴다면 꼭 참여해보세요. 단순히 “과제 풀어보고 끝”이 아니라, 다른 분들의 코드와 토론을 통해 시야가 넓어지는 경험을 할 수 있을 거예요.
제 부족한 경험이 여러분께 조금이나마 도움이 되었으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다! 🙏
위 글을 읽으시면서 궁금하신 점이 있다면 아래 댓글로 남겨주세요!👇


