[CS] 백엔드 ↔ 프론트 자료구조

이성우·2026년 1월 17일

개발을 하다 보면 같은 의미인데도 표현이 달라서 헷갈릴 때가 있다.
특히 백엔드와 프론트는 같은 데이터를 주고받으면서도 자료 구조를 부르는 이름과 실제 다루는 형태가 달라 오해가 생길 수 있다.

이 글에서는 자주 등장하는 자료 구조를 기준으로, 백엔드에서의 표현이 프론트(JavaScript)에서는 보통 어떤 형태로 매핑되는지 정리해보려고 한다.

❓ 자료 구조란

자료 구조(Data Structure)는 데이터를 저장하고 꺼내고 수정하는 방식형태로 정리한 것이다.
어떤 작업을 얼마나 자주/빠르게 할지 목적에 맞춰 선택하는 도구라고 볼 수 있다.


🔎 자료 구조의 종류

1. List ↔ Array

  • 백엔드: List<T>, ArrayList, Vector, list, ...
  • 프론트(JS): Array

핵심

  • JS 배열은 겉으로는 단순한 배열이지만, JS 엔진이 내부적으로 최적화를 많이 해서 리스트처럼 써도 괜춘

     리스트 → (추가/삭제가 되는 동적 목록)
    
  • 배열은 순회/변환에 강하지만, 키 기반 조회에는 약함

    • map/filter/reduce에는 적합. ID로 빠르게 찾기Map이 더 적합할 때가 많음.
  • 추가/삭제는 보통 뒤쪽(push/pop)에 하는 게 좋음

  • shift/unshift는 앞에서 요소를 넣고 빼면서 나머지 인덱스를 전부 재정렬해야 해서 비용이 커질 수 있음

예시 코드(프론트 기준)

// 백엔드에서 List<User> 내려옴 -> 프론트에서는 User[]
const users = res.users; 

console.log(Array.isArray(users)); // true

// 화면에서 필요한 조건만 필터링
const active = users.filter(u => u.active);

// 렌더링에 필요한 형태로 변환
const names = users.map(u => u.name);

2. Dictionary ↔ Map

  • 백엔드: Dictionary<TKey, TValue>, HashMap, dict
  • 프론트(JS): Map,Object({})

핵심

  • 둘 다 키로 값을 찾는 Key-Value 구조지만, 프론트에서는 상황에 따라 Map / Object 선택이 달라짐

  • Object({})이 더 나은 경우:

    • 키가 문자열로 고정이고, JSON으로 주고받고/저장하기 쉬워야 할 때
    • 예: 서버 응답 그대로 보관, localStorage 저장, DTO 그대로 사용
  • Map이 더 나은 경우:

    • 키가 문자열이 아닐 수도 있거나 조회/추가/삭제가 자주 일어나는 상태 관리 구조일 때
    • size, keys(), values(), entries() 같은 순회 API가 명확하고 일관됨
  • 주의점: Map은 JSON 직렬화가 기본으로 안 됨

    • 저장/전송이 필요하다면 Object.fromEntries(map) 또는 Array.from(map.entries())과 같은 JSON이 될 수 있는 형태(객체, 배열)로 변환해야 함

예시 코드(프론트 기준)

// 백엔드 Dictionary<string, User> -> 프론트에서는 { [id: string]: User }
const usersById = {
  u1: { id: 'u1', name: 'Kim' },
  u2: { id: 'u2', name: 'Lee' },
};


console.log(usersById['u2'].name); // Lee

3. Set

  • 백엔드: HashSet<T>, Set
  • 프론트(JS): Set

핵심

  • “선택됨/활성화됨/이미 처리됨 같은 상태 플래그에 좋음
  • 배열의 .includes()는 O(n), .has()는 O(1)

예시

const selectedIds = new Set();

selectedIds.add('m1');
selectedIds.add('m2');

if (selectedIds.has('m1')) {
  // 선택된 상태 처리
}

4. Queue / Deque (FIFO)

  • 백엔드: Queue<T>, Deque
  • 프론트(JS): 배열로도 가능하지만 .shift()가 자주 일어나면 안 좋을 수 있어서 포인터 큐 추천

핵심

  • 메시지 처리, 이벤트 버퍼, 작업 큐(순서대로 처리)에서 자주 사용
  • .shift() 대신 head 포인터를 쓰면 불필요한 재정렬을 피할 수 있음
  • 포인터 큐는 앞쪽에 사용 끝난 영역이 쌓이므로, 가끔 한 번씩 정리가 필요함
    • 매번 .shift()로 계속 당기는 게 아니라 “필요할 때만 한 번에 정리” 해서 .shift()사용을 줄이는 것

예시 (포인터 큐)

const q = [];
let head = 0;

q.push('A');
q.push('B');

const first = q[head++];  // 'A'
const second = q[head++]; // 'B'

// head가 너무 커지면 가끔 한 번 정리(압축)
if (head > 1000 && head > q.length / 2) {
  q.splice(0, head);
  head = 0;
}

5. Stack (LIFO)

  • 백엔드: Stack<T>
  • 프론트(JS): Array (push / pop)

핵심

  • Stack은 LIFO(Last In First Out) 구조: 마지막에 넣은 게 먼저 나감
  • JS에선 별도 자료구조 없이 배열의 push/pop으로 스택을 자연스럽게 구현할 수 있음
  • 실무에선 Undo/Redo, 히스토리, 최근 작업 되돌리기 같은 패턴에서 자주 등장

예시

const stack = [];

stack.push('A');
stack.push('B');

const last = stack.pop(); // 'B'

6. LinkedList

  • 백엔드: LinkedList<T>
  • 프론트(JS): 보통 그대로 구현할 일 거의 없음

핵심

  • 프론트 UI/상태 관리는 대부분 Array + id 조합으로 해결됨
  • 중간 삽입/삭제가 잦으니 LinkedList가 유리하지 않나? 싶어도 JS에선 이득이 크지 않은 경우가 많음
    (대신 Map/Set으로 조회/상태 관리를 최적화하는 게 체감이 더 큼)

7. Tree / Graph

  • 백엔드: 트리(메뉴), 그래프(연결관계), 인접 list 등
  • 프론트(JS):
    • 계층 UI: children: [] 형태의 중첩 구조
    • 빠른 조회/업데이트: 정규화(normalize)해서 byId 형태로 분리

핵심

  • 그대로 중첩 구조는 렌더링에 직관적
  • 정규화는 특정 노드만 빠르게 업데이트/조회할 때 유리함

예시 (정규화 느낌)

const nodesById = new Map();     // id -> node
const childrenById = new Map();  // id -> childIds[]
profile
안녕하세요!

0개의 댓글