모든 코딩 문제가 복잡한 자료구조를 요구하는 것은 아닙니다.
가끔은 지금 실행할지, 나중에 실행할지처럼 실행 시점을 다루는 작은 문제도 면접에서 나옵니다.
이번 글에서는 일정 시간이 지난 뒤 특정 작업을 실행하는 예약 함수를 만드는 문제를 정리해보겠습니다.
문제
어떤 프로그램에서는 작업을 즉시 실행하지 않고, 조금 뒤에 실행해야 하는 경우가 있습니다.
작업을 나타내는 함수 task와 기다릴 시간 delay가 주어질 때, delay 밀리초가 지난 뒤 task가 실행되도록 하는 함수를 작성해봅시다.
함수 형식은 아래와 같습니다.
schedule(task, delay)
task: 나중에 실행할 함수delay: 기다릴 시간(밀리초)
예를 들어 아래처럼 호출했다면:
schedule(show_message, 3000)
show_message는 약 3초 뒤에 실행되어야 합니다.
처음 떠오르는 생각
이 문제를 보면 처음에는 “그냥 함수 안에서 task()를 호출하면 되지 않을까?”라는 생각이 들 수 있습니다.
하지만 그렇게 하면 함수가 지금 바로 실행됩니다.
이 문제에서 필요한 것은 함수 호출 자체가 아니라, 호출 시점을 뒤로 미루는 것입니다.
즉 핵심은 아래 두 가지를 구분하는 데 있습니다.
- 함수 자체를 전달하는 것
- 함수를 즉시 호출하는 것
핵심 아이디어
이 문제의 핵심은 어렵지 않습니다.
- 함수를 다른 함수의 인자로 넘길 수 있어야 합니다
- 정해진 시간이 지난 뒤 실행되도록 예약해야 합니다
- 지금 실행하는 것과 나중에 실행하는 것을 구분해야 합니다
즉 task()를 바로 실행하는 것이 아니라,
일정 시간 후에 실행되도록 연결해야 합니다.
이 개념은 작은 타이머 문제처럼 보이지만, 사실은 함수를 값처럼 다루는 감각과 지연 실행의 흐름을 이해하고 있는지를 보는 문제에 가깝습니다.
코드
자바스크립트 풀이
자바스크립트에서는 이런 문제를 보면 보통 setTimeout을 떠올릴 수 있습니다.
function schedule(task, delay) {
setTimeout(task, delay);
}
파이썬 풀이
파이썬에서는 threading.Timer를 이용해 비슷한 기능을 만들 수 있습니다.
from threading import Timer
def schedule(task, delay):
Timer(delay / 1000, task).start()
파이썬 쪽에서는 입력이 밀리초 단위지만 Timer는 초 단위를 사용하므로, delay / 1000으로 바꿔주는 점을 기억해야 합니다.
실행 순서를 눈으로 확인해 보기
이 문제는 코드가 짧아서 단순해 보이지만, 실제로는 지금 실행되는 부분과 나중에 실행되는 부분을 분리해서 이해해야 합니다.
예를 들어 자바스크립트에서 아래 코드를 보겠습니다.
function showMessage() {
console.log("작업 실행");
}
console.log("시작");
schedule(showMessage, 2000);
console.log("예약 완료");
출력 흐름은 보통 이렇게 됩니다.
시작
예약 완료
약 2초 뒤...
작업 실행
즉 schedule(showMessage, 2000)를 호출했다고 해서 그 자리에서 바로 "작업 실행"이 찍히는 것이 아닙니다.
이 한 번의 흐름만 정확히 이해해도, task와 task()를 왜 구분해야 하는지가 훨씬 분명해집니다.
코드 해설
1) 자바스크립트에서는 setTimeout으로 예약합니다
setTimeout(task, delay);
setTimeout은 함수와 대기 시간을 받아, 지정한 시간이 지난 뒤 함수를 실행해줍니다.
즉 이 문제에서는 이미 언어가 제공하는 예약 도구를 올바르게 사용하는지가 중요합니다.
2) task와 task()는 완전히 다릅니다
setTimeout(task, delay);
이렇게 써야 하지,
setTimeout(task(), delay);
이렇게 쓰면 안 됩니다.
이유는 다음과 같습니다.
task는 함수 자체를 전달합니다task()는 함수를 지금 실행한 결과를 넘깁니다
즉 괄호를 붙이는 순간 예약 실행이 아니라 즉시 실행이 되어버립니다.
3) 파이썬에서는 밀리초를 초로 바꿔야 합니다
Timer(delay / 1000, task).start()
입력은 밀리초로 받았지만 Timer는 초를 기준으로 동작합니다.
그래서 2000 밀리초를 그대로 넣는 것이 아니라, 2.0초로 바꿔서 전달해야 합니다.
매개변수가 필요한 함수는 어떻게 예약할까?
지금 예시에서는 task가 인자를 받지 않는 함수였습니다. 그런데 실제로는 "이 메시지를 2초 뒤에 출력하라"처럼 값이 함께 필요한 경우도 있습니다.
자바스크립트에서는 보통 래퍼 함수를 하나 감싸서 전달합니다.
function showMessage(name) {
console.log(name);
}
schedule(() => showMessage("안녕하세요"), 2000);
파이썬도 비슷합니다.
def show_message(name):
print(name)
schedule(lambda: show_message("안녕하세요"), 2000)
이렇게 하면 showMessage("안녕하세요")를 지금 실행하는 것이 아니라, 나중에 실행될 함수 하나로 다시 감싸서 예약하는 구조가 됩니다.
실행 예시
자바스크립트 예시
function showMessage() {
console.log("작업 실행");
}
schedule(showMessage, 2000);
위 코드는 약 2초 뒤에 "작업 실행"을 출력합니다.
파이썬 예시
def show_message():
print("작업 실행")
schedule(show_message, 2000)
위 코드도 약 2초 뒤에 "작업 실행"을 출력합니다.
실수하기 쉬운 부분
task와 task()를 헷갈리면 바로 틀립니다
아래는 예약입니다.
setTimeout(task, delay);
아래는 즉시 실행입니다.
setTimeout(task(), delay);
문제는 "나중에 실행할 함수"를 넘기는 것이지, 지금 실행 결과를 넘기는 것이 아닙니다.
파이썬에서는 밀리초를 초로 바꿔야 합니다
Timer(delay / 1000, task).start()
delay가 2000이면 2000초가 아니라 2초가 되어야 합니다. 이 변환을 빼먹으면 실행 시점이 완전히 달라집니다.
예약 함수는 보통 바로 반환합니다
이 문제에서 schedule은 대기하다가 끝나는 함수가 아닙니다. 예약만 걸어두고 바로 반환하는 흐름으로 이해하는 것이 자연스럽습니다.
복잡도
이 문제는 직접 무거운 계산을 하는 유형은 아닙니다.
- 시간 복잡도:
O(1) - 공간 복잡도:
O(1)
핵심은 계산량보다, 언어가 제공하는 타이머 도구를 정확히 이해하고 쓰는 것에 있습니다.
정리
이 문제의 핵심은 한 줄로 정리할 수 있습니다.
함수를 지금 실행하지 말고, 지정한 시간이 지난 뒤 실행되도록 연결하는 것
즉 아래 세 가지를 이해하면 풀 수 있습니다.
- 함수 전달
- 시간 지연
- 나중 실행
겉으로는 간단해 보여도, 함수 자체를 넘기는 것과 함수를 바로 호출하는 것을 헷갈리면 틀리기 쉬운 문제입니다.
이 글의 문제 상황과 예시는 학습용으로 직접 재구성한 내용입니다.