JA

What finishing a CS fundamentals course changed about how I read code

Introduction

Before I started the computer science fundamentals course at Recursion, I honestly wondered whether writing code yourself still meant much in the age of AI.

At work I was handing more and more of the implementation to Claude Code and Copilot, and I had started to file “writing algorithms by hand” under general interest rather than anything load-bearing. If working code comes out of an AI, surely the skill that matters is reviewing it and letting it through.

Three months after finishing the course, that view has shifted quite a bit. Here are the three ways it changed.

Here is what this post covers:

What Recursion is

Recursion is an online learning platform built by a former software engineer in the US, teaching computer science from the ground up. The fundamentals course runs in three stages — beginner, intermediate, advanced — covering primitive types in the first, recursion in the second, and queues, stacks and trees in the third.

For what it’s worth, it took me about a year to solve every problem in the fundamentals on my own.

1. I read code at a finer resolution

I used to see code as a binary: it works, or it doesn’t. Now I read with questions like these running.

Is this loop justified?

When I see a nested loop, the first thing I ask is whether O(n²) is really necessary. Checking whether an array holds two elements summing to a target, written the obvious way, gives you a double loop and 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

Walk the array once and keep the values you have seen in a hash set (Python’s set, JavaScript’s Set) and the inner loop disappears, bringing it to O(n) on average. The cost is O(n) extra memory to remember what you have seen.

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

The trick is that “is target minus the current value among the values I have already seen” is a hash-set lookup, close to O(1) on average. When I see a nested loop in AI-written code, I now check whether this substitution applies before anything else.

Could this recursion be a loop?

I rarely write recursion directly at work. Even so, I think it is worth knowing how recursion and loops differ in the way they use memory.

Recursion pushes a stack frame on every call. A recursion of depth n has roughly n frames stacked at once by the time it reaches the bottom. A loop reuses its variables, so memory does not grow with the number of iterations.

Memory used by summing 1 to 5 with recursion versus with a loop. Recursion pushes a stack frame per call, so as many frames exist at once as the depth, while the loop rewrites the same variable on all five passes sum(5) with recursion sum(5)sum(4)sum(3)sum(2)sum(1)sum(0) → 0 6 frames aliveat the same time sum(5) with a loop 1st2nd3rd4th5th variable for the totalone box, rewritten 5 times same box every pass, so usage stays flat Both are O(n) time. Memory differs: O(n) against O(1)

Some work is more natural as recursion — walking a tree, say, which is awkward to write as a loop in the first place. Still, understanding why a loop is kinder to memory means that when an AI hands you recursion, you can judge whether rewriting it as a loop would be safer.

Is this the right data structure?

Sometimes you find code that adds elements while repeatedly pulling out the minimum or maximum, re-sorting the array every time. That is O(n log n) per operation. Swap in a heap (a priority queue) and both the insert and the minimum come out in O(log n). There is no need to reach for a heap where an array is fine, but a heap now comes to mind as an option whenever I see a requirement along the lines of “repeatedly take just the smallest or largest”.

Once you can see at this granularity, it pays off directly when reviewing AI-written code. I can now spot “this works today, but it will get heavy as the row count grows” and “this use of memory is wasteful”.

2. I think about time and space complexity

Here are some of the things I now have in mind.

Strings are objects, so they live on the heap

Strings in JavaScript and Python are variable-length reference-typed objects, so the actual data sits on the heap. Numbers and booleans are handled differently depending on the language, so here is a summary.

Kind of valueJavaScriptPython
StringObject on the heapObject on the heap
Number (integer-like)Primitive value, held directly where the variable livesObject (PyObject). The data is on the heap and the variable only holds a reference
BooleanPrimitive valueObject. True/False reuse singletons cached by the interpreter

JavaScript’s number and boolean are primitives, unlike strings, which is where the two split. In Python everything including int and bool is an object, so the distinction does not carry over.

Function calls are fast because they use the stack

More precisely: allocating and freeing stack memory is just moving a pointer, so it is O(1). The heap has an allocator searching for free space, which costs more for the same size.

Once that difference is clear, you can also explain how heavy a seemingly small design decision like “turn this bool into an enum” actually is. Java’s boolean, a primitive, can be represented in a single bit and sits directly as a value in a local variable on the stack or in an object’s field. A Java enum, on the other hand, is effectively a class instance, and each enum constant exists as one object in memory. The variable holds a reference pointing at that value rather than the value itself, so reading it means following the object’s location once every time. (C# implements enums as value types, so none of this applies there.) So replacing a bool with an enum in Java buys you the design benefit — more states, easier to read — but also a runtime cost: the code itself gets slower by the creation and dereferencing overhead. When I decide to add states for readability, I am now aware that this cost is part of the choice.

A feel for stack overflow in recursion

The space complexity of recursion is set by the maximum number of frames active at once, not by the total number of calls. Recursion of depth n uses O(n) of stack space. The stack, unlike the heap, is a fixed size, so recursion that goes too deep blows straight past the limit and crashes.

Inserting at the front of a list is O(n)

Inserting at the front of an array-backed list means shifting every existing element one position back. That is why it is O(n) rather than O(1). It is fundamentally different in cost from appending at the end.

Inserting an element at the front of an array. Before the insert, A, B, C and D sit at positions 0 to 3; inserting a new element X at the front shifts A, B, C and D each one position back, leaving X, A, B, C, D. Every existing element moves, which makes it O(n) Before A0B1C2D3 After (X added at the front) X0A1B2C3D4 All n existing items shift back one slot, so the insert is always O(n)

3. How it plays out at work

The biggest one is that when I look at AI-written or existing code, I can now take it back to the AI myself: “this is inserting at the front of a list — won’t that get slow?”

I used to settle for anything that ran. Now, the moment I see where an insert happens, I can judge whether the volume makes it a non-issue or whether the O(n) will stack up and start to bite. Even when I cannot tell, I can argue it out with the AI on concrete grounds — insertion point, expected row count, how often it runs — which means we can weigh alternatives together, like switching to a queue or a deque.

The other thing that helps is how much easier it is to put questions into words when asking about AI-written or existing code. The fundamentals course covered higher-order functions, and once I started reading front-end code I saw the “pass a function as an argument” pattern all over the place. Having had the words for that pattern in advance made the investigation go smoothly: “this is passing a function as a value”, “this argument is a callback that runs later”. Sorting out the concepts first speeds up both how fast I read code and how precisely I can question the AI.

Where I felt the payoff most clearly was a hiring coding interview in February 2026. The problem was “given any number, return the sum of the primes below it”. Writing the primality check, I knew that testing divisors up to √n is enough to decide whether n is prime, so instead of naively dividing from 2 to n-1 I went straight to an implementation bounded by O(n√n). A sieve of Eratosthenes makes primality easier still, but I will leave that aside here.

The divisors of 36 laid out as pairs of a smaller and a larger factor. Going 1×36, 2×18, 3×12, 4×9, 6×6, the smaller factor climbs from 1 to 6 while the larger falls from 36 to 6, meeting at √36 = 6. Checking divisors above 6 only turns up pairs already seen, in reverse 1 × 36 = 36 2 × 18 = 36 3 × 12 = 36 4 × 9 = 36 6 × 6 = 36 smaller factor 1 to 6 is enough the fold (same pairs) They meet at √36 = 6, so there is no need to check 7 and above

Before, I could not have explained why the check can stop at √n, and my hand would have stalled. Being able to implement it while explaining the reasoning out loud, in front of the interviewer, came from having built that understanding beforehand.

Why the age of AI is exactly when you need to have written code yourself

A human is the one who ultimately decides whether AI-written code ships. A human is also the one who answers for it when a bug or an outage follows.

Taking that responsibility requires being able to explain, in your own words, what the code the AI produced actually does. Without being able to say why an implementation works, you cannot review it, cannot approve it, and cannot respond when something goes wrong. This fundamentals course was time spent building the reading ability that holds up that explanation.

Wrapping up

What I got from the fundamentals course was less the ability to solve problems than the ability to put into words what is happening when I look at code.

To that first question — is there any point writing code yourself? — I would now answer: you need the experience of writing it to keep enough explanatory power to stand behind what the AI produces.

Recursion