ソフトウェアエンジニアの面接は、ほとんどの企業で決まった流れをたどります。リクルーターとの電話、1〜2回のコーディング面接、中級・シニア職向けのシステム設計面接、そして行動面接です。以下では、ラウンドごとに分けた20のソフトウェアエンジニア面接質問と回答を紹介します。すべてのコードサンプルは、公開前に総当たり版と比べて、数千のランダムな入力でテストしました。
各ラウンドで見られていること
| ラウンド | 一般的な形式 | 見られているポイント |
|---|---|---|
| リクルーター面接 | 電話で20〜30分 | 経歴、志望動機、年収レンジ、日程などの条件 |
| コーディング | 共有エディタで45〜60分 | 問題解決力、きれいなコード、テスト、作業中に話せるか |
| CS基礎 | コーディングに組み込み、または別ラウンド | 普段使っているシステムを理解しているか |
| システム設計 | ホワイトボードまたはドキュメントで45〜60分 | トレードオフ、スケール、わかりやすい説明 |
| 行動面接 | 30〜45分 | 当事者意識、協働、失敗への向き合い方 |
新卒の面接は主にコーディングと行動面接です。シニア職の面接ではシステム設計が増え、過去のプロジェクトについてより深い回答が求められます。リクルーターとの電話については、電話スクリーニングの面接質問をご覧ください。
コーディング問題
どのコーディング面接でも、コードを書く前にアプローチを説明し、時間計算量と空間計算量を述べ、最後に小さな例でテストしてください。計算量の部分については、Big O チートシートで扱っています。
1. 二数の和:合計が目標値になる2つの数の添字を返す
各数の添字をハッシュマップに保存します。新しい数が来るたびに、その補数がすでにマップにあるかを確認します。
def two_sum(nums, target):
seen = {}
for i, x in enumerate(nums):
if target - x in seen:
return [seen[target - x], i]
seen[x] = i
return []
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]
時間計算量はO(n)、空間計算量はO(n)です。総当たり版は全ペアをO(n²)で調べますが、面接官は通常、まずそれに言及してから改善することを求めます。
2. 有効な括弧:すべての括弧が正しい順序で閉じられているかを確認する
開き括弧をスタックに積みます。閉じ括弧が来たら、スタックの先頭がその対応する開き括弧でなければなりません。
def is_valid(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
else:
stack.append(ch)
return not stack
print(is_valid("([]{})"), is_valid("(]")) # True False
時間計算量はO(n)、空間計算量はO(n)です。最後の行が重要です。「((」のような文字列はスタックに要素が残るため、有効ではありません。
3. 重なる区間をマージする
開始時刻でソートし、直前にマージした区間を延長するか、新しい区間を始めます。
def merge_intervals(intervals):
merged = []
for start, end in sorted(intervals):
if merged and start <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], end)
else:
merged.append([start, end])
return merged
print(merge_intervals([[1, 3], [2, 6], [8, 10], [15, 18]]))
# [[1, 6], [8, 10], [15, 18]]
ソートがあるため、時間計算量はO(n log n)です。[1, 2] と [2, 3] のように端がちょうど接する区間を統合すべきかを、必ず確認しましょう。この版では統合しています。
4. 連結リストを反転する
リストを一度たどり、各ノードが前のノードを指すように変えます。
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev = None
while head:
head.next, prev, head = prev, head, head.next
return prev
時間計算量はO(n)、追加の空間計算量はO(1)です。多重代入が面接官にとって凝りすぎていると感じられる場合は、一時変数を使って3行に書き直してください。短く書くより、わかりやすさを優先しましょう。
5. 出現回数が多いk個の要素を返す
ハッシュマップで数を数え、ヒープを使って出現回数が大きいk個を取り出します。
from collections import Counter
import heapq
def top_k_frequent(nums, k):
counts = Counter(nums)
return [x for x, _ in heapq.nlargest(k, counts.items(), key=lambda p: p[1])]
print(top_k_frequent([1, 1, 1, 2, 2, 3], 2)) # [1, 2]
時間計算量はO(n log k)です。フォローアップとして、出現回数によるバケットソートが考えられ、それならO(n)で解けます。
6. get と put がどちらもO(1)のLRUキャッシュを設計する
高速な検索と、最近使われた順での高速な並べ替えが必要です。ハッシュマップと双方向連結リストを組み合わせると、その両方を満たせます。Pythonでは、OrderedDictがまさにこの組み合わせです。
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return -1
self.data.move_to_end(key)
return self.data[key]
def put(self, key, value):
if key in self.data:
self.data.move_to_end(key)
self.data[key] = value
if len(self.data) > self.capacity:
self.data.popitem(last=False)
一部の面接官は、連結リストを自分で実装するよう求めます。まず構造を説明しましょう。マップは連結リストのノードを指し、最も新しいノードは一方の端にあり、削除は反対側の端から行います。
7. 壁のあるグリッドでの最短経路
幅優先探索は、各ステップのコストが同じであれば最短経路を見つけられます。
from collections import deque
def shortest_path(grid):
rows, cols = len(grid), len(grid[0])
if grid[0][0] or grid[rows - 1][cols - 1]:
return -1
queue = deque([(0, 0, 0)])
seen = {(0, 0)}
while queue:
r, c, steps = queue.popleft()
if (r, c) == (rows - 1, cols - 1):
return steps
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and not grid[nr][nc] and (nr, nc) not in seen:
seen.add((nr, nc))
queue.append((nr, nc, steps + 1))
return -1
print(shortest_path([[0, 0, 0], [1, 1, 0], [0, 0, 0]])) # 4
時間計算量と空間計算量はどちらもO(rows × cols)です。セルはキューから取り出すときではなく、キューに追加するときに訪問済みとしてマークしてください。そうしないと、同じセルが何度もキューに入ってしまいます。
8. 最初の不良バージョンを見つける
バージョン1からnまでは、ある時点までは正常で、その後は不良です。二分探索を使えば、O(log n)回の確認で最初の不良バージョンを見つけられます。
def first_bad_version(n, is_bad):
lo, hi = 1, n
while lo < hi:
mid = (lo + hi) // 2
if is_bad(mid):
hi = mid
else:
lo = mid + 1
return lo
このパターンは、一度だけ答えが切り替わるイエス・ノー型の問題すべてに使えます。この種の問題はよく出題されます。これらのパターンを扱う問題の一覧は、Blind 75をご覧ください。
CS基礎の質問
9. プロセスとスレッドの違いは何ですか?
プロセスは、独自のメモリ空間を持つ実行中のプログラムです。スレッドはプロセスの中で動き、そのメモリを共有します。スレッドは作成や切り替えのコストが小さく、データを直接共有できます。しかし、その共有があるからこそロックが必要になり、競合状態が起きます。プロセスは互いに隔離されているため、ひとつがクラッシュしても、他には影響しません。
10. TCPとUDPの違いは何ですか?
TCPは接続を確立し、再送とフロー制御によって、順序どおりで信頼性の高い配信を保証します。UDPは独立したパケットを送り、配信や順序の保証はなく、そのぶんオーバーヘッドが小さいです。Webページ、API、ファイル転送にはTCPを使います。遅れて届いたパケットがどのみち役に立たない音声通話、ビデオ、ゲームにはUDPが向いています。さらに詳しくは、コンピュータネットワークの面接質問をご覧ください。
11. ブラウザにURLを入力してEnterを押すと、何が起きますか?
ブラウザはDNSでドメインをIPアドレスに変換し、TCP接続を開き、HTTPSの場合はTLSハンドシェイクを行います。HTTPリクエストを送り、サーバー(多くの場合ロードバランサーの背後)がレスポンスを返します。ブラウザはHTMLを解析し、参照されているCSS、JavaScript、画像を取得して、ページを描画します。面接官が選んだ部分を、さらに掘り下げて説明してください。
12. ハッシュテーブルはどう動き、その時間計算量はどれくらいですか?
ハッシュ関数が各キーを配列のバケットに対応づけます。検索、挿入、削除は平均してO(1)です。2つのキーが同じバケットに入った場合(衝突)は、チェイン法か別のスロットを探す方法で処理し、混み合ってくると再ハッシュします。多くのキーが衝突すると、最悪の場合はO(n)になります。
13. NoSQLではなくSQLデータベースを選ぶのは、どんなときですか?
データがリレーショナルで、結合やトランザクションが必要で、スキーマがかなり安定しているとき(注文、決済、ユーザーアカウントなど)はSQLを選びます。アクセスパターンが単純で、データの形がさまざまであるとき、または多数のマシンに書き込みをスケールさせる必要があるときは、ドキュメント型やキーバリュー型のストアを選びます。両方を使うシステムも多くあります。面接官が求めているのは勝者を決めることではなく、トレードオフの説明です。その上に置かれることの多いAPI層については、REST API の面接質問で扱っています。
システム設計の質問
14. URL短縮サービスを設計する
まず要件を明確にします。1日に何件の新規リンクが作られるか、何回読まれるか、リンクに有効期限があるか、カスタムエイリアスを許可するか。そのうえで、API(作成とリダイレクト)、短縮コードの生成方法(62進数にエンコードしたカウンタか、一意性確認つきのランダムコード)、ストレージ(コードからURLへのキーバリューストア)、人気リンク向けのキャッシュを説明します。読み取りは書き込みよりはるかに多いからです。時間があれば、最後に分析と不正利用の防止を加えます。設計全体の流れは、当社のシステム設計ガイドで解説しています。
15. レートリミッターを設計する
まず、どこで動かすか(APIゲートウェイ、ユーザーごと、またはAPIキーごと)と、クライアントが上限を超えたときにどうするか(再試行までの時間を含むHTTP 429)を決めます。一般的な選択肢はトークンバケットです。各クライアントに、一定の速度で補充され最大トークン数を持つバケットがあり、リクエストごとにトークンを1つ使います。短いバーストを許しつつ、平均レートを守れます。複数のサーバーがある場合は、カウンタをRedisのような共有ストアに置き、どのサーバーも同じ数を見るようにします。さらに練習したい場合は、システム設計の面接質問をご覧ください。
行動面接の質問
各エピソードは、状況、自分が何をしたか、結果の順に明確に組み立ててください。数字がある場合は、それも入れます。
16. 自己紹介をしてください
キャリアを2分程度で話し、最後にこの職種を志望する理由で締めくくります。具体的な結果のあるプロジェクトを1つか2つ入れてください。自己紹介の答え方のガイドに例があります。
17. 解決した難しいバグについて教えてください
回答例:「当社のチェックアウトサービスで、1日に数回、規則性のないタイムアウトが発生するようになりました。リクエストIDをログに追加したところ、すべてのタイムアウトが、カートに50件を超える商品があるときにだけ実行されるデータベースクエリから来ていることがわかりました。そのクエリは結合のキー列にインデックスがありませんでした。インデックスを追加した結果、遅いクエリは約4秒から50ミリ秒未満に下がり、次回は早めに気づけるよう、そのクエリのレイテンシーにアラートを設定しました。」
18. チームメイトとの意見の対立について教えてください
回答例:「あるチームメイトは通知サービスを新しいフレームワークで書き直したいと考えていましたが、私はリリース前にリスクが高すぎると考えました。会議で言い合う代わりに、それぞれがコストとリスクを書き出すことを提案しました。私たちは、リリース前に現在のコードの最も深刻な問題を2つ直し、書き直しは次の四半期に計画することで合意しました。リリースは予定どおり行われ、書き直しは2か月後に、適切なテスト計画を立てて実施しました。」
19. 失敗したプロジェクトについて教えてください
自分も一部の責任を負っていた、実際の失敗を選んでください。何がうまくいかなかったのか、次は何を変えるか、その後に実際に何を変えたかを説明します。面接官が見ているのは、完璧な実績ではなく、誠実さと学びです。
20. なぜこの会社で働きたいのですか?
会社の製品やエンジニアリングの仕事の具体的な点と、自分の経験を結びつけてください。「2年間、御社のAPIを使ってきたので、プラットフォーム側の仕事に関わりたいと考えています」は、「素晴らしい会社だからです」よりずっと良い答えです。
面接官への質問
- このチームで典型的な1週間はどのようなものですか?
- 仕事はどのように計画され、優先順位は誰が決めますか?
- ここではコードレビューとデプロイはどのように行われていますか?
- 最初の6か月で成功とはどのような状態ですか?
- チームが取り組んでいる最も難しい技術的課題は何ですか?
さらに詳しくは、面接の最後に聞くべき質問をご覧ください。
準備の方法
- 練習するプログラミング言語を1つに決め、すべての練習問題で使いましょう。コードが短くなるので、Pythonが最も一般的な選択です。言語そのものについては、Python の面接質問で扱っています。
- 問題数ではなくパターン別に練習します。ハッシュマップ、2ポインタ、スライディングウィンドウ、二分探索、BFSとDFS、ヒープ、動的計画法です。
- タイマーを使って、声に出しながら問題を解きます。正しくても黙って解いた解答より、わかりやすく話しながら解いた解答の方が高く評価されます。
- 実際に使うエディタで練習します。多くの企業はCoderPadを使っており、CoderPadには無料のサンドボックスがあります。
- 自分の仕事から、ほとんどの行動面接の質問に応用できる5つのエピソードを用意します。
- 中級・シニア職では、少なくとも数回は誰かにフォローアップ質問をしてもらいながら、システム設計の練習を通しで行ってください。
コーディング面接を声に出して練習する
YesToTheOfferは面接での回答を文字起こしするので、解法をどう説明したかを振り返れます。画面のスクリーンショットから0.2倍速でコーディング支援も受けられます。30分無料、サブスクリプションなし。



