Most "repos you should star" lists are useless because they treat reading code like collecting trading cards. You star fifty repositories, feel productive, and learn nothing, because starring is not reading and reading is not studying. The repos that actually changed how I write code are a short list, and I studied each of them with a specific question in mind: how did they solve a problem I was about to solve badly?
So this is not a list of the most popular projects. It is a handful of repositories I have genuinely read, often while building something on my own stack, with the specific lesson I took from each. Some are well known, some are not, but in every case the value was in opening the source and reading how a hard thing was done well, not in the README.
How to actually study a repo
Before the picks, the method, because it is the part most people skip. Cloning a repo and scrolling the file tree teaches you nothing. What works for me is to arrive with a question. "How does this library make its types feel magical?" "How does this tool stay this fast?" Then I find the one file or module where that question is answered and I read it properly, including the tests, because the tests are where the author tells you what the code is actually supposed to do under pressure.
The other habit that pays off is reading the git history and the issues, not just the current code. The current code is the answer; the history is the reasoning. A commit that reverts a clever optimization with the message "this was not worth the complexity" is worth more to your judgment than the optimization itself.
There is also a quality I look for before I commit time to a repo at all, and it is readability over cleverness. The code that teaches you the most reads almost boringly, like prose, where the next line is the one you expected. Here is the difference I mean, the same grouping written two ways.
const r = a.reduce((m,x)=>(m[x.k]=(m[x.k]||[]).concat(x),m),{});The repos that taught me most chose the second style. Clarity is a feature, and it is the one you actually learn from.
When a repo consistently picks the second style, I know an afternoon spent reading it will pay off, so that is the signal I lean on most.
Laid out as a sequence, here is how one of these reading sessions actually unfolds for me, start to finish.
I never open a repo to "learn it". I open it to answer something concrete, like how Drizzle keeps its types feeling magical without codegen. The question is the whole session in one sentence, and everything else narrows from it.
That four-step loop is small enough that I can finish it in an hour, which is the only reason the habit survives a real schedule.
Why readable code beats clever code in the repos worth studying
I used to admire the dense one-liner, the kind that packs four operations into a single expression and dares you to follow. Studying real repositories cured me of that. The clever version impresses you once and then resists you every time you return to it, because you have to rebuild the author's reasoning from scratch. The readable version costs the author a few more lines and gives every future reader the idea for free. Between shifts at BMW and coursework for my M.Sc. at LMU Munich, I rarely get to read the same file twice, so code that explains itself on the first pass is the only kind I can actually learn from. The deeper payoff is transfer. A clever trick is welded to its context, so you cannot lift it into your own project without dragging the whole puzzle along. A clear pattern, a named variable, an explicit loop, a small function that does one thing, detaches cleanly. You see it, you understand the shape, and you reach for the same shape next week in code that looks nothing like the original. Clarity is what makes a pattern portable, and portable patterns are the entire point of studying someone else's work.
drizzle-orm: predictable abstractions over magic ones
I use Drizzle in production on adatepe.dev, so I read its source out of self-interest, and it changed how I think about library design. The lesson is restraint. Drizzle's query builder maps closely to SQL instead of inventing its own universe, and the schema is just TypeScript objects rather than a separate DSL. The genius is in what it refuses to hide. Compare it to ORMs that generate queries you cannot predict; Drizzle decided that predictability was worth more than convenience.
// You can read this and know exactly what SQL leaves your machine.
db.select().from(users).where(eq(users.id, id));
The deeper lesson for your own code: an abstraction that hides too much becomes impossible to debug, and the best libraries pick a layer to abstract and then get out of the way. Study how Drizzle draws that line and you will draw better lines in your own modules.
zustand: how small a good state library can be
Zustand is worth reading precisely because it is tiny. The core of the library is a remarkably small amount of code, and reading it is a lesson in how much capability you can deliver without ceremony. There is no provider pyramid, no boilerplate, no opinions forced on you. It does one thing, a subscribable store, and trusts you with the rest.
What I took from it is a question I now ask of my own utilities: how much of this is essential and how much is ritual I copied from somewhere? Zustand is the counter-argument to every over-engineered state solution. Read the store implementation and you will see that the "hard" problem of state management is, at its core, a subscribe function and a setter. The rest of the industry built cathedrals on top of that; Zustand built a door.
The gotcha that reading the source taught me, and that I would have hit in production otherwise, is the selector. The naked useStore() call with no argument subscribes your component to the entire store, so any unrelated state change re-renders it. On adatepe.dev I had a header component re-rendering on every keystroke in a search box three layers away, simply because both read from the same store without selectors. The fix was one line per call, useStore((s) => s.theme) instead of useStore(), and the render count for that header dropped from roughly forty per typed query to two. The source makes the reason obvious: the store calls every subscriber's listener on each setState, and the selector plus an equality check is the only thing that decides whether your component actually re-renders. Nothing about that is hidden, but you only internalize it by reading the forty lines that implement it rather than the API docs that describe it. That is the entire argument for studying small libraries: the bug you avoid by understanding the mechanism is worth more than the hour you spent reading it. I wrote the full version of this trap up separately in my Zustand note, but the meta-lesson is that a library small enough to read end to end is a library whose footguns you can see coming. You cannot say that about a framework you only ever grep.
tRPC: types as the contract between client and server
tRPC is the project that made type safety across the network boundary feel obvious in hindsight. The idea, end-to-end types with no code generation step, sounds like it should require dark magic, and reading how it is actually wired together is genuinely educational. It leans on TypeScript inference to a degree that taught me how much the type system can do if you design for it from the start.
Even if you never use tRPC, the lesson transfers. The boundary between two parts of your system is where bugs breed, and tRPC's answer, make the type system enforce the contract so a mismatch fails at compile time instead of in production, is a pattern you can apply to your own internal boundaries. I think about this every time I define the shape passed from a server component to the client.
biome: a rewrite that questioned the whole pipeline
Biome is interesting not just as a fast formatter and linter but as an argument. It looks at the sprawling JavaScript tooling pipeline, a separate formatter, a separate linter, each with its own config and plugins, and asks whether that fragmentation is necessary at all. Reading how it unifies these into one fast Rust binary is a study in challenging assumptions everyone else treats as fixed.
The lesson is meta: sometimes the biggest improvement is not optimizing a step but questioning whether the step should exist separately. I run a format-typecheck-lint gate on my own projects, and Biome made me think harder about how many tools that gate really needs. Studying a tool that consolidates an entire category teaches you to spot the same opportunity in your own setup.
Four I have genuinely read, and the one lesson each left me with. Switch between them:
Lesson: predictable abstractions beat magic ones. The query builder maps closely to SQL and the schema is just TypeScript objects.
The genius is in what it refuses to hide. An abstraction that hides too much becomes impossible to debug, and the best libraries pick a layer to abstract and then get out of the way.
Read the tests before you read the code
I mentioned this in the method, but it deserves its own argument because almost nobody does it. When you open an unfamiliar repository, the source files tell you how the author solved the problem, but the test files tell you what the problem actually was, including all the edge cases the author discovered the hard way. A function's signature is the author's intention; its tests are the author's scar tissue. The scars are where the real learning is.
This is most striking in libraries that look deceptively simple. A date utility's implementation might be fifty lines, but its test suite encodes years of accumulated knowledge about leap seconds, time zones that changed their rules, and inputs no sane person would expect. Reading those tests is a crash course in everything that is hard about a domain you assumed was easy. I learned more about the genuine difficulty of parsing by reading a parser's test fixtures than by reading the parser, because the fixtures are a museum of every way the input can betray you.
When I write my own libraries now, I treat the test file as documentation I am writing for the next person who opens the repo with a question. If I read other people's tests to understand their reasoning, the least I can do is leave mine readable for the same purpose.
How to find these repos in the first place
The practical question I get is where these repositories come from, since they rarely top the trending page. The honest answer is that I find them through dependency archaeology. When a library I use does something impressively, I open its source and look at what it depends on, and the small focused dependencies underneath are often the real gems. The famous library is the cathedral; the obscure utility it quietly relies on is frequently the more elegant thing to study.
The other reliable source is the code of tools you already trust. If you like how a project is built, read how its maintainers built it, then follow their other repositories, because good engineers tend to write consistently good code across projects. Following a person whose judgment you respect is a far better discovery mechanism than any algorithmic feed, which optimizes for popularity rather than for the quiet correctness you actually want to learn from.
And occasionally the best repository to study is one you cannot find at all, because it does not exist, and the lesson is that you should write it. Some of the most useful things I learned came from building a small library because no good one existed, then later reading how someone else had solved the same problem and seeing exactly where my version fell short. That comparison, your honest attempt against an expert's, teaches more than reading either in isolation.
If you are not sure where to start, here is the routing I actually use when someone asks me which repo to crack open first.
Which repo should you study first?
Pick based on the question you actually have right now, not on star count.
What are you trying to get better at?
One more prediction about where the real gems hide:
Before the smaller gems, a confession about which repos actually moved me the most.
That trait, readability you can hold in your head, is exactly why the next exercise works.
A smaller gem: read a single-purpose library end to end
The most underrated study exercise is not a famous repo at all. It is finding a small, single-purpose library you depend on, something like a date utility, a tiny validation helper, a focused parser, and reading the entire thing in one sitting. Small libraries are the only ones you can hold in your head completely, and a library you fully understand is one you can fix, extend, or replace instead of fear.
When I built openAIScientist as an R package, the most useful preparation was reading other small, well-structured packages cover to cover to understand the conventions, the test layout, the way a clean public API is shaped. You cannot get that from a tutorial. You get it from reading code written by someone who cared, which is the entire point of studying repositories in the first place.
Next time you open a repo to learn from it, run the session against this. An hour of reading code this way beats a week of tutorials, because tutorials show the happy path and source shows the real one:
Reading a changelog as a design document
There is one file I now open before almost anything else, and it is not in the source tree at all. It is the CHANGELOG, or the release notes, or whatever the project uses to record what changed and why. The git history tells you the reasoning behind a single line; the changelog tells you the reasoning behind the project. It is the place where maintainers stop and say, out loud, what they decided mattered enough to write down for the people who depend on them.
A good changelog is a sequence of judgment calls made visible. You see what the maintainers chose to add, but more interestingly you see what they reversed: a feature shipped in one version and quietly removed two versions later, with a sentence explaining that it caused more confusion than it solved. That single line of regret is worth a chapter of design advice, because it is a decision someone already paid for. The tests show you the edge cases the author hit; the changelog shows you the design bets the author lost.
Breaking changes are the most instructive part. How a project handles a breaking change, whether it deprecates first, gives a migration path, and explains the cost honestly, tells you more about the maintainers' respect for their users than any README promise. The way a team versions its work is an ethical posture, not just a number, and the changelog is where that posture is recorded over time.
When I ship updates to adatepe.dev or cut a release of openAIScientist, I try to write changelog entries the way I wish other people wrote theirs: not "fixed bugs" but the actual reasoning, the thing I tried that did not work, the constraint I was trading against. If someone studies my repos one day, that file is the cheapest gift I can leave them.
A reading order for a small repo, and what to keep
When I sit down with a small repo I do not read top to bottom, because the file tree is alphabetical and the logic is not. I start at the entry point, the thing the README tells you to import or run, because that is the front door the author actually designed for. From there I follow the call into the first real function and stop chasing the moment I understand the shape rather than every branch.
Next I read the types, or in an untyped language the data structures and the function signatures. The types are the author's mental model written down, and once I have them the rest of the code reads like prose instead of a puzzle. Only then do I open the tests, because by that point I know enough to recognise which cases are the interesting ones and which are routine.
Between LMU coursework for my M.Sc. and shifts at BMW, I have refined this into a fixed sequence I run every time, and writing it down kept me honest about the order. Here is exactly how I move through an unfamiliar repo and why each step earns its place.
1. README # what + why
2. package.json # deps = the worldview
3. src/index.ts # the entry point
4. types / models # the core nouns
5. tests # intended behaviorThe README and dependencies tell you what the project believes about the problem before you read a single function. Context first makes the code readable.
This sequence sounds rigid, but it is what keeps a reading session from drifting into aimless file-tree scrolling. Each step narrows the question for the next, so by the tests I already know which cases matter.
What I extract is never the whole file. It is one move: how this library shapes its public API, how it isolates the messy part, how it names the thing I always struggle to name. I keep a plain markdown file I half-jokingly call my pattern library, and each entry is a few lines, a link to the exact commit, and the one sentence I want to remember. Between classes at LMU and shifts at BMW I do not have time to re-read a repo twice, so the note is the point. Over a year those notes turn into instincts, and the instincts are what actually show up in the code I ship.
How I keep a study habit without it becoming a chore
The fastest way to kill a study habit is to make it heavy. I tried the ambitious version once, blocking out a whole weekend to read a big framework cover to cover, and I burned out before the second file. What actually survives my schedule is a small weekly cadence: one short session, one repo, one question. Between shifts at BMW and coursework for my M.Sc. in CS at LMU Munich, an hour I can reliably find beats three hours I keep promising myself and never spend. Small and repeated wins over large and aspirational every time.
The second thing that keeps it light is taking exactly one pattern per repo into my own notes. Not a summary, not a tutorial I will never reread, just the single move that struck me, written in a sentence with a link to the commit. If I try to capture everything, the note becomes a second job and I stop writing it. One pattern is cheap enough that I always do it, and cheap habits are the ones that last. This is the same discipline I lean on when turning a GitHub repo into a portfolio asset: keep the unit of work small enough that you actually finish it.
I also read diffs and pull requests, not only the final code. The merged code is the verdict; the PR discussion is the trial. Reading why a reviewer pushed back, why an approach was abandoned, why a smaller version won, teaches me the reasoning that the polished file hides. A clean function tells you the answer. The PR that produced it tells you the three answers the author rejected first, and those are usually the more useful lesson.
The last rule is the one I had to learn the hard way: resist the urge to read everything. A repo is not a checklist to complete. I read until I understand the one shape I came for, then I stop, even when there is more. Finishing the question is the goal, not finishing the codebase. Letting myself stop is exactly what keeps me coming back next week instead of dreading it.
The habit, not the list
If you take one thing from this, let it not be the list. Lists go stale and every project here will eventually be replaced by something newer. Take the habit: when you are about to build a hard thing, find one or two repositories that have already built that hard thing well, arrive with a specific question, and read the relevant code and its tests properly. An hour of that is worth more than a week of tutorials, because tutorials show you the happy path and source code shows you the real one.
I write up what I learn from the code I read, alongside the projects it feeds into, on /blog and /#projects. The repositories worth your time are the ones that answer a question you actually have. Go find your question first; the right repo to study tends to follow from it.
Which lesson do you want to go deeper on?
Pick a repo from above, here's where I put what I learned into practice.
Reading repositories this carefully is half my hobby and half how I learn, and the projects below are where those lessons quietly ended up.