그저 내가 되었고

🌟신입 백엔드 개발자 기술 면접 질문&답변(1~10) 본문

개발/BE 일반*개발 이야기

🌟신입 백엔드 개발자 기술 면접 질문&답변(1~10)

hyuunii 2022. 12. 19. 21:18

예로 나는 spring framework를 사용하고 mvc 패턴을 이용하여 웹앱을 만들어본것이 그동안 했던것이라면

기술면접은 왜 spring을 썼고 왜 mvc패턴을 썻고 테이블 설계 는 어떻게했고 JVM의 동작원리는 어떻게 되는것이며 List는 어떤걸썻고 왜 arrayList를 썼으며 등등...

 

1. Algorithm; 시간복잡도와 공간복잡도가 무엇인지 설명

어떤 알고리즘이 있을 때 그 알고리즘의 성능 평가를 위해 복잡도를 사용합니다.
시간복잡도는 특정 알고리즘이 어떤 문제를 해결하는데 걸리는 시간을 의미하며,
공간 복잡도는 작성한 프로그램이 얼마나 많은 공간 즉 메모리를 차지하느냐를 분석합니다.
(시간 복잡도에는 최악의 경우를 계산하는 방식인 빅-오Big-O 표기법이라는 개념이 있습니다. 예제로는 O(1) : 스택의 Push, Pop/O(log n) : 이진트리/O(n) : for 문/O(n log n) : 퀵 정렬(quick sort), 병합정렬(merge sort), 힙 정렬(heap Sort)/O(n²): 이중 for 문, 삽입정렬(insertion sort), 거품정렬(bubble sort), 선택정렬(selection sort)/O(2ⁿ) : 피보나치 수열등이 있습니다.)
동일한 기능을 수행하는 알고리즘이 있을 때 복잡도가 낮을수록 좋은 알고리즘이라고 합니다.
최근엔 대용량 메모리 시스템이 보편화되어 시간 복잡도가 우선됩니다.

 

2. CS-Data Structure; 스택, 큐에 대해 설명

스택은 '쌓다'라는 의미로, 데이터를 순서대로 차곡차곡 쌓아 올린 형태의 자료구조입니다.
스택은 정해진 한방향으로만 자료의 입출력이 가능한데, 생활속 비슷한 예시로는 프링글스 안의 감자칩이나 쌓여진 종이컵을 생각해 볼 수 있습니다.
스택은 마지막으로 넣은 것이 가장 먼저 나오는 LIFO(Last In First Out, 후입선출)의 구조를 갖고 있습니다.
큐는 스택과는 다르게 먼저 들어온 것이 먼저 나가는 FIFO(First In First Out, 선입선출)의 구조를 가지고 있습니다.
일반적으로 은행에서 번호표를 먼저 뽑은 사람이 창구에서 먼저 불리는 것을 비슷한 생활속 예시로 생각해 볼 수 있습니다.
FIFO를 실현하기 위해서 큐는 스택과 다르게 입출구가 각각 나뉘어져 있습니다. 이때 출구는 머리(front)로 정해 삭제 연산(deQueue)만 수행하고, 입구는 꼬리(rear)로 정해 삽입 연산(enQueue)만 수행합니다.

 

3. CS-Data Structure; 배열, 링크드리스트 비교 설명

두 자료구조 모두 데이터를 나열한다는 점에서는 비슷합니다.
다만 배열은 미리 정해진 특정 크기만큼의 연속된 메모리 공간에 데이터를 저장하는 자료구조이며,
연결리스트는 각각의 데이터가 메모리 공간에 떨어져 있으며 이 데이터들은 앞의 데이터와 뒤의 데이터의 주소를 기억합니다.
그러므로 배열은 데이터의 처음 주소만 알면 임의의 데이터에도 인덱스를 활용해 쉽게 접근할 수 있습니다.
그러나 데이터를 중간에 추가하거나 삭제할때는 해당 위치 기준으로 뒤쪽의 모든 데이터를 밀거나 당겨야 하므로 비효율적입니다.
연결리스트는 특정 데이터에 임의 접근이 불가능합니다. 처음부터 순차적으로 주소를 탐색해나가야 하기 때문입니다.
하지만 데이터를 추가하거나 삭제할때는 데이터가 가진 메모리 주소 값만 갈아끼워주면 되기 때문에 효율적입니다.
(마지막으로 배열은 스택 영역에 메모리 할당이 되고, 연결리스트는 힙 영역에 할당이 됩니다.)
(두 자료구조의 시간복잡도: https://m.blog.naver.com/raylee00/221944085465)

 

4. CS-Data Base; 트랜잭션 설명 + 원자성/일관성/고립성/지속성 설명

트랜잭션이란 SELECT, UPDATE, INSERT, DELETE 등의 연산을 수행하여 DB의 상태를 변화시키기 위해 수행하는 작업의 단위 또는 한꺼번에 수행되어야 할 일련의 연산이며 이는 작업의 완전성을 보장합니다.
즉, 작업 셋을 모두 완벽하게 처리하지 못할 경우에는 아예 원상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어줍니다.
ACID는 트랜잭션의 특징입니다.
원자성(Atomicity)는 트랜잭션 내의 모든 명령이 DB에 전부 반영되어 완벽히 수행되던지 혹은 모두가 반영되지 않아 전혀 수행되지 않아야 함을 말합니다.
일관성(Consistency)은 트랜잭션의 작업 처리 결과가 항상 일관성 있어야 함을 말합니다.
독립성(Isolation)은 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도 다른 트랜잭션의 연산에 끼어들 수 없음을 말합니다.
지속성(Durability)은 트랜잭션이 성공적으로 완료되었을 경우 결과는 영구적으로 반영되어야함을 말합니다.
((쉽게 이해하기: https://bactoria.tistory.com/entry/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-4%EA%B0%80%EC%A7%80-%ED%8A%B9%EC%A7%95-ACID)

 

5. CS-Data Base; 정규화(Normalization) 설명 및 대표적 장단점

Relational DB 설계시 중복을 최소화하도록 데이터를 구조화하는 프로세스를 정규화라고 합니다.
이를 통해 데이터의 무결성을 유지하고 DB 저장 용량을 효율적으로 관리할 수 있습니다.
장점으로는 불필요한 데이터(Redundancy)를 없앨 수 있고, 데이터 삽입/갱신/삭제 시 발생할 수 있는 각종 이상현상(Anamolies)들을 방지할 수 있는 점 등이 있으며,
단점으로는 길이가 짧은 데이터 생성으로 JOIN의 사용이 과도하게 증가되어 응답시간이 느려질 수 있다는 점 등이 있습니다.
(Redundancy & Anamolies: https://velog.io/@busybean3/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B4%EC%83%81%ED%98%84%EC%83%81)

 

6. Web; CORS 설명 + 어떻게 허용할지

Cross-Origin Rersource Sharing 즉 교차 출처 리소스 공유는 단어 그대로 다른 출처의 리소스 공유에 대한 허용/비허용 정책입니다.
브라우저의 SOP(Same Origin Policy)에 따라 다른 출처의 리소스를 차단하기때문에 만나는것이 CORS 에러입니다.
즉, 다시 말해 CORS 정책에 따르면 SOP를 위반하더라도 다른 출처의 리소스라도 허용할 수 있습니다.
이를 위해서는 서버에서 응답 헤더에 Access-Control-Allow-Origin 이란 필드를 추가하고, 그 값에 접근하는 것이 허용된 출처를 기재해서 클라이언트에 응답해야합니다.
이때 Origin에 *을 사용하면 모든 Origin의 요청을 허용한다는 의미이므로 당장은 편하지만,
알 수 없는 이상한 출처에서 오는 요청까지 모두 허용하기 때문에 보안은 더 허술해집니다.
그러므로 허용하려는 출처를 구체적으로 직접 명시하는것이 필요합니다.

 

7. JS; var vs let vs const

JS에서 변수를 선언할 때 var, let, const를 사용합니다.
var가 가지는 여러 문제들 때문에 2015년에 출시된 ES6에 const와 let이 추가되었습니다.
var는 동일한 이름으로 여러 번 중복 선언이 가능하며, 마지막에 할당된 값이 변수에 저장됩니다.
이는 유연하게 변수를 사용할 수 있다는 장점이 될 수도 있지만, 코드량이 많아졌을 때 같은 이름의 변수명을 여러번 사용했다면 어느 부분에서 문제가 발생했는지 파악하기 힘들뿐더러 값이 바뀔 우려가 있습니다.
et과 const는 이러한 var를 보완합니다.
let은 중복 선언은 불가능하며 재할당은 가능합니다.
constant(상수)를 뜻하는 const는 한 번만 선언이 가능하며 중복 선언과 재할당 모두 불가능합니다.
var의 사용은 권장되지 않으며, const를 기본으로 사용하여 불필요한 변수의 재사용을 방지하고, 만약 재할당이 필요한 경우 let을 사용하는 것이 좋습니다.

 

8. JS; 🔥Promise 설명

ES6에서 프라미스가 도입되어 지금처럼 널리 사용되기 이전에는 주로 콜백 함수를 다른 함수의 인자로 넘겨서 비동기 처리 코딩을 했었습니다. 
단순한 코드를 작성할 때는 기존에 하던대로 콜백 함수로 비동기 작업을 해도 별 문제되지 않습니다. 그러나 콜백을 연쇄적으로 호출하는 복잡한 코드는 소위 '콜백 지옥'이라 불리며 가독성이 상당히 떨어집니다. 이를 해결하기 위해 여러 방법들이 논의되었으며 개중 하나가 프라미스입니다.  
프라미스는 말 그대로 "지금은 없으니까 이따가 줄게"하는 약속입니다. 더 정확히는 "지금은 없는데 이상 없으면 이따가 주고 있으면 알려줄게."하는 약속이죠.
(I/O나 Network를 통해서 데이터를 얻는 경우가 대표적인데, CPU에 의해서 실행되는 코드 입장에서는 엄청나게 긴 지연 시간으로 여겨지기 때문에 Non-blocking 코드를 지향하는 자바스크립트에서는 비동기 처리가 필수적입니다.)
프라미스는 Promise 생성자 함수를 통해 인스턴스화됩니다. Promise 객체는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데 이 콜백 함수는 resolvedhk reject 함수를 인자로 전달받습니다.
이때 비동기 처리가 성공하면 resolve 함수를 호출하고, 프로미스는 fulfilled 상태가 됩니다.
비동기 처리가 실패하면 reject 함수를 호출하고 프로미스는 rejected 상태가 됩니다.
 promise로 구현된 비동기 함수를 호출하는 측(promise consumer)에서는 promise 객체의 후속 처리 메소드(then, catch)를 통해 비동기 처리 결과 또는 에러 메시지를 전달받아 처리합니다. then 메소드는 두 개의 콜백 함수를 인자로 전달받으며, 첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 상태)시 호출되고, 두 번째 콜백 함수는 실패(rejected, reject 함수가 호출된 상태)시 호출됩니다. catch 메소드는 예외 즉 비동기 처리에서 발생한 에러 또는 then메소드에서 발생한 에러를 만날시 호출됩니다. promise로 구현된 비동기 함수는 언제나 promise 객체를 반환하므로 앞의 두 메소드 모두 Promise를 반환합니다.
미동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩(nesting)되어 복잡도가 높아지는 콜백 헬이 발생합니다. 프라미스는 후속 처리 메소드를 체이닝하여 여러 개의 프로미스를 연결하여 사용할 수 있으며, 이로써 콜백 헬은 해결됩니다.
Promise객체를 반환한 비동기 함수는 프라미스 후속 처리 메소드인 then이나 catch 메소드를 사용할 수 있습니다. 따라서 then 메소드가 Promise 객체를 반환하도록 하면(기본적으로 then이든 catch든 promise를 반환하긴 함) 여러개의 프라미스를 체이닝하여 사용할 수 있습니다.
(참고: https://springfall.cc/post/7 //// https://poiemaweb.com/es6-promise)

+추가: 콜백코드 vs 프라미스코드

//콜백
findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});

function findUserAndCallBack(id, cb) {
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    const user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
    cb(user);
  }, 100);
}


//프라미스
findUser(1).then(function (user) {
  console.log("user:", user);
});

function findUser(id) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log("waited 0.1 sec.");
      const user = {
        id: id,
        name: "User" + id,
        email: id + "@test.com",
      };
      resolve(user);
    }, 100);
  });
}


//결과
waited 0.1 sec.
user: {id: 1, name: "User1", email: "1@test.com"}

 

9. JS; Hoisting 설명

변수나 함수가 어디서 선언되는지 상관 없이 모두 유효 범위의 최상단으로 끌어 올려지는 듯한 현상을 말하는 JS의 매커니즘입니다.
그러므로 유효한 스코프에서는 어디서든 해당 변수나 함수를 참조할 수 있습니다.
JS 엔진에서 변수는 선언, 초기화, 할당의 과정을 거쳐 생성됩니다. 이때 var의 경우 변수의 선언 및 초기화가 함께 진행되므로 호이스팅시 변수 접근이 가능합니다.
let과 const의 경우 선언과 초기화를 각각 진행합니다.
그러므로 호이스팅시 선언 단계에서 실행 컨텍스트에 등록은 했지만, 메모리를 할당받지 못해 참조할 메모리가 없으므로 초기화 전까지 TDZ 구간에 걸려 ReferenceErrorr가 발생합니다.
함수 선언문의 경우 함수의 선언, 초기화, 할당이 동시에 진행되므로 호이스팅은 물론 실행도 가능합니다.
그러나 함수 표현식의 경우 변수는 호이스팅이 되지만, 함수를 할당 즉 초기화하는것은 호이스팅 되지 않기 때문에 변수를 함수가 아닌 일반 변수로 취급하여 TypeError를 발생시킵니다.

함수 선언식

function 함수명() {
  구현 로직
}

 

함수 표현식

var 함수명 = function () {
  구현 로직
};