Writing
You Don’t Need to Remember Everything to Be a Good Software Engineer
After more than two decades in software, I still look things up. That isn’t something I’m embarrassed about anymore. There is simply too much to know, and the amount you need to understand only grows as your career goes on.
By Ka Lun Chan · Learning / Engineering judgment / AI
I still look things up, every week
I’ve worked with Python and Django, Ruby on Rails, React and Next.js, SQL databases, Git, AWS, Docker, Kafka, APIs, authentication and distributed systems. I’ve been a co-founder and CTO, led teams, and reviewed more pull requests than I could count. I still open the docs for things I’ve done hundreds of times.
Earlier in my career, I treated that as a gap I needed to close. If a senior engineer had to search for how to rebase onto a different branch, what did that say about them? It took me years to see it the other way around. The engineers I trusted most looked things up constantly. They just knew exactly what to look for, and they could tell quickly when the answer they found was wrong.
Here’s a fairly honest list of things I’ve looked up recently, next to the things I didn’t need to.
| Area | What I looked up | What I already understood |
|---|---|---|
| Git | The argument order for git rebase --onto, and how to dig a lost commit out of git reflog. | Commits form a graph, branches are just pointers, and almost nothing is gone until garbage collection runs. |
| SQL | Window function syntax like ROW_NUMBER() OVER (PARTITION BY …), and the exact ON CONFLICT upsert form in Postgres. | How indexes, joins and the query planner work, and why the same query is fine on ten thousand rows and painful on ten million. |
| Django ORM | When to use select_related versus prefetch_related, and how Subquery and OuterRef fit together. | The ORM writes SQL for you. A queryset evaluated inside a loop is an N+1 problem waiting for production data. |
| Kafka | Consumer settings like max.poll.interval.ms and enable.auto.commit, and what producer acks values actually guarantee. | Partitions, consumer groups and offsets. At-least-once delivery means every handler has to be safe to run twice. |
| AWS | IAM condition keys, S3 bucket policy syntax, and the default idle timeout on a load balancer. | Least privilege, where each timeout sits in the request path, and what breaks when one layer gives up before another. |
| JWT | Which registered claims to validate (exp, nbf, aud, iss) and how much clock-skew leeway a library allows. | A JWT is signed, not encrypted. Once it’s issued, you can’t revoke it without keeping extra state somewhere. |
| Docker | Multi-stage build syntax, and the flags for cleaning up old images and volumes. | Layers cache in order, and a container is an isolated process, not a small virtual machine. |
| Performance | How to read EXPLAIN ANALYZE output, profiler flags, and the curl options that break a request into timings. | Measure before changing anything. Most of the slowness I’ve chased was waiting on I/O, not burning CPU. |
The middle column changes all the time. Flags get renamed, APIs get new versions, cloud consoles move things around. The right column changes slowly, and most of it carries over from one tool to the next. It’s also the part that actually lets me do the job.
Do software engineers need to remember everything?
No. Nobody remembers everything, and trying to is a poor use of your attention. What good engineers carry around is a working model of how systems behave. Syntax is cheap to retrieve. Understanding is not.
Looking something up does not mean you don’t know what you’re doing. Often it means the opposite. You know enough to recognize the problem, you know roughly what the answer should look like, and you want to confirm the details before you put them into a system other people depend on.
I’d much rather work with an engineer who checks the docs than one who confidently types a flag from memory and gets it slightly wrong.
Memorizing syntax vs. understanding how a system works
These are two different kinds of knowledge, and they fail in different ways.
If you forget syntax, you find out immediately. The code doesn’t compile, the command errors, the test fails. You look it up and move on in a few minutes.
If you don’t understand the system, you often don’t find out at all, at least not right away. The code runs. The tests pass. Then it meets real traffic, real data or a real failure, and it behaves in a way you didn’t predict.
A pattern I’ve run into more than once is a Kafka consumer group that keeps rebalancing under load. Messages get processed twice, lag climbs, and the logs fill up with partition reassignments. I don’t keep the exact property names in my head. But I know that if a consumer takes too long between polls, the group decides it’s dead and hands its partitions to someone else. That gives me the right question to ask. From there, finding max.poll.interval.ms and max.poll.records takes a minute, and so does checking whether offsets are committed before or after the work is done.
Understanding tells you where to look. Search tells you the exact words. You need both, but only one of them is hard to replace.
The fundamentals that keep paying off
When I say fundamentals, I don’t mean trivia or algorithm puzzles. I mean the ideas that show up underneath every framework I’ve used:
- How data is stored, indexed and queried, and what that costs as it grows.
- How requests move across a network: DNS, TCP, TLS, HTTP, proxies, timeouts and retries.
- Processes, memory, concurrency, and what happens when two things touch the same state.
- Failure: what your system does when a dependency is slow, down, or returns something unexpected.
- Security basics: authentication versus authorization, trust boundaries, least privilege, and where secrets live.
- Debugging: form a hypothesis, shrink the problem, change one thing at a time, and read the actual error.
I started my career in technical support and network operations, long before I was writing much application code. A lot of what I learned there still shows up in my work. When a web request times out, I think about every hop between the browser and the database, because I spent years watching real traffic cross real networks. Frameworks have come and gone since then. That mental model hasn’t.
Fundamentals also make new tools easier to pick up. Rails and Django look different, but once you understand request lifecycles, ORMs, migrations and background jobs, you’re mostly learning where things live and what they’re called.
Knowing when something doesn’t look right
One of the most useful skills I’ve built isn’t knowing the answer. It’s the feeling that something is off before I can fully explain why.
In code review, it usually looks like one of these:
- A migration that rewrites or locks a large table, scheduled to run at peak traffic.
- A database query inside a loop that looks harmless with twenty rows of test data.
- A retry with no backoff, pointed at a service that’s already struggling.
- An access token with a very long expiry, stored somewhere any script on the page can read it.
- A cache with no clear story for how it gets invalidated.
- An endpoint that checks whether you’re logged in, but not whether you’re allowed to see that record.
- A response that comes back suspiciously fast, which sometimes means it never did the work.
None of these take memorized syntax to spot. They come from seeing systems fail, reading other people’s postmortems, and fixing my own mistakes. You don’t get that from a cheat sheet. You get it from building things, running them, and paying attention when they break.
What senior software engineers need to know keeps growing
Early on, I thought seniority meant knowing more about the code. It does, but that turned out to be the smaller part.
As you move into senior, staff and leadership roles, the list of things you need to understand gets wider, not narrower:
- Architecture
- How the pieces fit, and what it will cost to change them later.
- Security
- Threat models, data exposure, and who can do what.
- Infrastructure
- Networking, deployment, observability, and the bill at the end of the month.
- Delivery
- How work gets from an idea to production safely, and how to keep it moving.
- Requirements
- What the business actually needs, which isn’t always what the ticket says.
- Technical debt
- Which shortcuts are fine, which ones will hurt, and when to pay them down.
- Mentoring
- Helping other engineers grow into the decisions you used to make.
- Communication
- Explaining a technical risk to someone who doesn’t need the details but does need to decide.
- Business constraints
- Deadlines, budgets, contracts, compliance, and the size of the team you actually have.
- Tradeoffs
- There’s rarely a perfect answer, only one that fits this situation better than the others.
As a co-founder and CTO, most of my hardest days had nothing to do with syntax. The questions were more like: Should we build this at all? What happens when it fails at two in the morning? Who is going to maintain it? Can we ship something smaller first? None of those have an answer you can paste in.
Nobody holds all of that in their head in full detail. What you can do is understand each area well enough to ask good questions, know who to bring in, and notice when a decision in one area creates a problem in another. I’ve written more about that side of the work on my engineering leadership page.
What changes with AI, and what doesn’t
AI tools have changed how I work. I use them to recall syntax, draft boilerplate, explain an unfamiliar codebase, and sketch a first version of something I’d otherwise spend an hour scaffolding. Retrieving information and generating code are both much faster than they used to be.
What hasn’t changed is who is responsible for the result. An AI answer is a starting point, and it’s only as useful as your ability to judge it. You still need enough understanding to:
- Ask the right question. A vague prompt gets a plausible, generic answer. A precise one comes from knowing what matters in your system.
- Validate the answer. Does it do what it claims? Does it handle the edge cases you care about?
- Recognize incorrect assumptions. Generated code often assumes a different library version, a different schema, or a simpler world than the one you run in.
- Understand the tradeoffs. There are usually several ways to solve a problem, and the first one offered isn’t always right for your constraints.
- Debug it when it doesn’t work. Sometimes it won’t, in ways that only make sense if you understand what’s underneath.
- Decide whether it fits the system. Code that works in isolation can still be wrong for your architecture, your team or your security model.
I’ve seen generated code that looked clean and would pass a quick review: a Django view that ran one query per row once real data showed up, a JWT check that never validated the audience claim, a Kafka consumer that committed offsets before the work was done. Each one would have worked in a demo. Each one is the kind of thing you only catch if you already understand how the system behaves.
So I don’t think AI makes understanding less important. It does the opposite. When information gets easier to retrieve, the scarce skill is knowing what to do with it: which answer to trust, which to question, and which to throw away.
Why I build small tools
There’s a thought I have a lot. I keep looking this up. Or I keep working this out by hand, drawing the same diagram on a whiteboard, or explaining the same idea to someone new.
At some point I stop and ask: why don’t I just build a small tool for this?
That habit is a big part of why I build side projects like Useful Little Tools, a collection of small, focused browser tools. Some started because I needed a quick answer and didn’t want to open a spreadsheet again. Others started because I wanted to understand something properly, and building it was the fastest way to find the gaps in what I thought I knew.
Small tools are a good way to learn because they’re honest. You can’t hand-wave a calculation you have to implement. You find out quickly which edge cases you forgot, which formula you only half remembered, and what you assumed without checking.
They pay off three ways. I learn something while building them. I have something to reuse the next time the problem comes up. And now and then someone else hits the same problem, finds the tool, and saves themselves twenty minutes. For larger systems I write up the decisions instead, like the case studies on my projects page.
How I keep learning as a software engineer
I don’t have a grand system. These are the habits that have stuck:
- Read the official docs, not just the first answer. A snippet tells you what to type. The docs tell you why, and what the defaults are.
- Reproduce it small. When something confuses me, I build the smallest version that shows the behavior: a throwaway script, a local container, a two-table database.
- Write down anything I’ve looked up twice. If I searched for it twice, I’ll search for it a third time. A short note saves that time.
- Read the source when it matters. Frameworks are just code. Seeing how Django builds a query or how a client library retries teaches more than most articles, including this one.
- Explain it to someone else. Mentoring is one of the best ways I know to find out what I don’t really understand.
- Build something small. See above.
- Stay curious outside my lane. Some of my most useful lessons came from support tickets, sales calls and budget reviews, not code.
Short answers
Do experienced developers still Google things?
Yes, all the time. Experienced developers look up syntax, flags and configuration constantly. The difference is that they know what to search for, and they can tell quickly whether the answer fits their system.
Is it bad if I can’t remember syntax?
No. Syntax is easy to retrieve and changes often. It’s more valuable to understand how the system works, so you know what to look for and can spot when something is wrong.
What should software engineers focus on learning?
Fundamentals that carry across tools: data and databases, networking, concurrency, failure handling, security basics and debugging. As you grow more senior, add architecture, delivery, communication and tradeoffs.
Does AI make fundamentals less important?
No. AI makes it faster to retrieve information and generate code, but you still need to ask the right questions, validate the output, catch wrong assumptions and decide whether a solution fits your system. Understanding becomes more valuable, not less.
What I’d tell a newer engineer
Being a strong engineer isn’t about remembering everything. It’s about:
- Understanding the fundamentals.
- Knowing how to find what you need.
- Recognizing when something doesn’t look right.
- Continuing to learn.
I still look things up. I expect I always will. The difference is that I no longer read it as a sign I’m behind. It’s part of the job. So is the learning, and that part is never finished.