EN

Recursionのコンピュータサイエンスの基礎コースを終えて、実務におけるコードの見え方がどう変わったか

はじめに

Recursionのコンピュータサイエンスの基礎コースを始める前は「AI時代にコードを自分で書くこと、そんなに意味あるのか?」と正直思っていました。

実務でもClaude CodeやCopilotに実装を任せる比重がどんどん増えていて、アルゴリズムを手で書く時間なんて「趣味の教養」くらいの位置づけでいいんじゃないかと考えていたんです。動くコードがAIから出てくるなら、それをレビューして通すスキルの方が大事だろう、と。

その考えは、コースを終えて3ヶ月経った今、割と変わりました。ここでは、その変化を3つ書きます。

この記事の内容は次のとおりです。

Recursionとは

Recursionは、米国の元ソフトウェアエンジニアが開発した、コンピュータサイエンスを基礎から学べるオンライン学習プラットフォームです。基礎コースは初級・中級・上級の3段階で構成されていて、初級でプリミティブな型などを、中級で再帰を、上級でキュー、スタック、木構造を学びます。

ちなみに私は、コンピュータサイエンスの基礎の問題を全部自力で解くまでに、だいたい1年くらいかかりました。

1. コードへの解像度が上がった

以前は、コードを「動くか動かないか」の二択でしか見れていませんでした。 今は、たとえば次のようなことを考えながら読んでいます。

このループは妥当か?

ネストしたループを見ると、まず「本当にO(n²)が必要か」を考えるようになりました。 たとえば「配列の中に、合計がtargetになる2つの要素があるか」を調べる処理は、素直に書くと二重ループでO(n²)になります。

for i in range(len(arr)):
    for j in range(len(arr)):
        if i != j and arr[i] + arr[j] == target:
            return True

一度だけ配列を走査して、見た値をハッシュ集合(Pythonのset、JavaScriptのSet)に入れておけば、内側のループをなくして平均O(n)に落とせます。ただし見た値を覚えておくためにO(n)の追加メモリが必要になります。

seen = set()
for x in arr:
    if target - x in seen:
        return True
    seen.add(x)

「targetから今の値を引いた数が、すでに見た値の中にあるか」をハッシュ集合で平均O(1)に近い速さで判定できるからです。AIが書いたコードでネストしたループを見ると、まずこの置き換えができないかを考えるようになりました。

この再帰はループに置き換えられないか

実務で再帰をそのまま書く機会はあまりありません。それでも、再帰とループでメモリの使われ方がどう違うかは知っておいた方がいいと思っています。

再帰は呼び出すたびにスタックフレームを積みます。深さnの再帰は、最深部に達するまでn個前後のフレームが同時に積まれた状態になります。ループは変数を使い回すだけなので、繰り返しの回数が増えてもメモリの使用量は変わりません。

1から5までの合計を求める処理を再帰とループで書いた場合のメモリ使用量の比較。再帰は呼び出すたびにスタックフレームが積まれ深さの分だけ同時に存在するのに対し、ループは5回とも同じ変数を書き換えるだけで済む 再帰でsum(5)を計算 sum(5)sum(4)sum(3)sum(2)sum(1)sum(0) → 0 6個のフレームが同時に存在する ループでsum(5)を計算 1周目2周目3周目4周目5周目 合計を保持する変数同じ箱を5回書き換えるだけ 5回とも同じ箱に書き込むので使用量は一定 どちらも計算量はO(n)。使用メモリはO(n)とO(1)で違う

再帰の方が自然に書ける処理もあります(木構造の走査など、そもそもループでは書きにくいもの)。それでも「なぜループの方がメモリに優しいのか」を理解しておくと、AIが再帰で書いてきたコードを見たとき、「これはループに書き換えた方が安全では」と判断できるようになります。

このデータ構造の選択は妥当か

たとえば、最小値・最大値を取り出す処理を何度も挟みながら要素を追加していくコードで、そのたびに配列をソートし直しているものを見かけることがあります。これだと1回の操作にO(n log n)かかります。ヒープ(優先度付きキュー)に置き換えれば、追加も最小値の取り出しもO(log n)で済みます。 配列で十分な処理にヒープを持ち出す必要はありませんが、「頻繁に最小値・最大値だけを取り出したい」という要件を見たときに、選択肢としてヒープが浮かぶようになりました。

このくらいの粒度で見えるようになると、AIが書いたコードをレビューするときにもそのまま役立ちます。 「今は動くが、件数が増えると重くなる」「このメモリの使い方には無駄がある」と見抜けるようになりました。

2. 時間・空間計算量を意識するようになった

たとえば、こんなことを考えるようになりました。

文字列はオブジェクトなのでヒープに乗る

JavaScriptやPythonの文字列は可変長・参照型のオブジェクトなので、実体はヒープに置かれます。ただし数値や真偽値の扱いは言語によって違うので、まとめておきます。

値の種類JavaScriptPython
文字列ヒープ上のオブジェクトヒープ上のオブジェクト
数値(int的な値)プリミティブ値。変数の場所に直接収まるオブジェクト(PyObject)。実体はヒープ上にあり、変数は参照を持つだけ
真偽値(bool)プリミティブ値オブジェクト。True/Falseはインタプリタがキャッシュしたシングルトンを使い回す

JavaScriptのnumberやbooleanは文字列と違ってプリミティブ値なので、この点で扱いが分かれます。Pythonはint・boolも含めてすべてがオブジェクトなので、この区別はそのまま当てはまりません。

関数呼び出しがスタックだから速い

正確には、スタックのメモリ確保・解放はポインタを動かすだけでO(1)です。 ヒープはアロケータが空き領域を探す処理があるので、同じサイズでもコストが高くなります。

この違いがわかると、「boolをenumに変える」という一見小さな設計判断が、実はどれくらい重いのかも説明できるようになります。 たとえばJavaのboolean(プリミティブ型)はtrue/falseの1bitで表現でき、スタック上のローカル変数やオブジェクトのフィールドに値として直接乗ります。 一方でJavaのenumは実質的にクラスのインスタンスで、各列挙定数はメモリ上に1つずつ存在するオブジェクトとして扱われます。つまり、値そのものではなく「その値を指す参照」を持つ形になるので、値を読むたびに1回、オブジェクトの場所をたどる必要があります。 (同じ「enum」でもC#は値型として実装されているため、この話には当てはまりません)。 つまりJavaでboolをenumに置き換えると、「状態が増えて読みやすくなる」という設計上のメリットだけでなく、生成コストと参照コストが増えるぶんコード自体も遅くなるという実行時の代償が発生します。 可読性のために状態を増やす判断をするときは、このコストも込みで選んでいると意識できるようになりました。

再帰でスタックオーバーフローする感覚

再帰の空間計算量は、呼び出し回数の総数ではなく「同時にアクティブなフレームの最大数」で決まります。 深さnの再帰はO(n)のスタック領域を使います。 スタックはヒープと違って固定サイズなので、深すぎる再帰はそのまま上限を突破してクラッシュします。

リストの先頭挿入がO(n)になる

配列ベースのリストに先頭から要素を挿入すると、既存の要素を全部1つ後ろにシフトする必要があります。 だからO(1)ではなくO(n)です。 末尾に追加するのとは、根本的にコストが違います。

配列の先頭に要素を挿入する図。挿入前はA、B、C、Dが0から3の位置に並んでおり、先頭に新しい要素Xを挿入すると、A、B、C、Dはそれぞれ1つずつ後ろの位置にずれ、挿入後はX、A、B、C、Dの並びになる。既存の要素すべてが移動するのでO(n)になる 挿入前 A0B1C2D3 挿入後(先頭にXを追加) X0A1B2C3D4 既存のn個を全部1つ後ろにずらすので、挿入は必ずO(n)になる

3. 実務にどう効いてきたか

一番大きかったのは、AIが書いたコードや既存コードを見て「これ先頭からリストに入れてるけど遅くならない?」とAI自身に相談できるようになったことです。

以前は動いていればそれで良しとしていましたが、今はリストへの挿入位置を見た瞬間に「この処理量なら問題ないか、それともO(n)が積み重なって効いてくるか」を判断できるようになりました。 判断がつかないときも、具体的な根拠(挿入位置・想定件数・実行頻度)を挙げてAIと議論できるので、代替案(キューやdequeへの変更など)を一緒に検討できるようになりました。

もう一つ効いているのは、AIが書いたコードや既存コードについて質問するときの、言語化のしやすさです。 この基礎コースでは高階関数を扱いましたが、実際にフロントエンドのコードを見ると「関数を引数として渡す」パターンが多く見受けられました。 このパターンを事前に言語化できていたおかげで、「これは関数を値として渡している」「この引数はコールバックとして後で実行される」といった調査がスムーズにできるようになりました。 概念を先に整理していると、コードを読むスピードも、AIへの質問の的確さも上がると感じています。

受講の効果を一番わかりやすく感じたのは、2026年2月に受けた採用時のコーディング面接でした。 問題は「任意の数を与え、その数より下の素数の合計を返す」というものです。 素数判定の関数を書く際、ある数nが素数かどうかは√n以下の数で割れるかを調べれば十分だとわかっていたので、単純に2からn-1まで割っていく実装ではなく、最大でO(n√n)に収まる実装に迷いなく落とし込めました。エラトステネスの篩を使えばもっと楽に素数判定できますが、ここでは割愛します。

36の約数を小さい方と大きい方のペアで並べた図。1×36、2×18、3×12、4×9、6×6の順に、小さい方の約数は1から6へ増え、大きい方の約数は36から6へ減っていき、√36=6で一致する。6より大きい約数を調べても、すでに調べた組み合わせが順番を変えて出てくるだけ 1 × 36 = 36 2 × 18 = 36 3 × 12 = 36 4 × 9 = 36 6 × 6 = 36 小さい方の約数 1→6だけ調べれば十分 ここは折り返し (左と同じ組み合わせ) √36 = 6で一致するので、7以降の約数を調べる必要はない

以前ならこの判定条件をなぜ√nで打ち切れるのか説明できず、筆も止まってました。 面接官の前でその場で理由を説明しながら実装できたのは、事前に理解を積んでいたからだと思います。

なぜAI時代だからこそ自分で書く経験が要るのか

AIが書いたコードを採用してリリースするかどうかを最終的に決めるのは人間です。バグや障害が起きたときに責任を取るのも人間です。

責任を持つには、AIが出したコードの処理を自分の言葉で説明できる必要があります。「なぜこの実装で動くのか」を説明できなければ、レビューも承認も、事故が起きたときの対応もできません。この基礎コースは、その説明力を支える読む力の土台を作る時間でした。

まとめ

この基礎コースで得たのは、問題を解く力そのものより「コードを見たときに何が起きているかを言語化する力」でした。

「自分でコードを書く意味あるの?」という最初の疑問には、今なら「AIの出力に対して責任を持てるだけの説明力を保つために、書く経験が必要」と答えます。

Recursion