Faragiri

Learn LanguagesCode

Nuno's tool ran on his machine and failed on a colleague's. The code was identical; one uncommitted lockfile cost him a day.

5Sections
5Min read
2Depth

One language, cut through

The tutorial is the core, and the day is lost in the rings around it

Learning moves outward from the middle. A program stops working on a second computer somewhere in the outer two rings, which no course covers.

  1. the language itselfThe syntaxLoops, functions and how it spells a list. A weekend, and the part everybody means by learning a language
  2. 02The standard libraryLooked up rather than learned, and correctly so
  3. 03The package managerOne per language, all the same shape, none of them mentioned in chapter one
  4. 04The lockfile and the runtime versionThe two things that differ between two computers running identical code
  5. 05The build, the tests and publishingWhere a project stops being a file and becomes something that can be handed over

It says nothing about difficulty. The outer rings are not harder than the core, they are simply not taught, which is a different problem with a different fix.

Nuno Ferreira wrote a 200 line tool that turns a folder of invoices into one summary sheet, and it worked. He sent it to a colleague in the same office, who ran it and got an error on line 4 about a function that did not exist. The code was identical. The versions of one library underneath it were not, and the whole of the second day went on that.

The syntax of the language took Nuno a weekend. The part that cost him a day is not syntax at all, and it is the same part in every language on this topic: how a project says what it depends on, and how a second machine is made to agree.

What a language actually asks you to learn

A tutorial covers the loops, the functions, the way the language spells a list. That material is real and it is also the small half. Underneath it sits a package manager, a lockfile, a build step, a test runner, a formatter and a way of publishing, and none of those appear in the tutorial because none of them are the language. They are the reason a working program stops working on somebody else's computer, which is the event that actually ends most people's first project.

The useful thing is that the shape underneath is the same everywhere. Python's uv, JavaScript's npm and Rust's cargo are the same tool with different names: each reads a file listing what the project needs, resolves that into exact versions, and writes those exact versions down. Learning the shape once means the second language costs a fraction of the first, and it is why what the choice actually depends on, and what it does not matters less than people expect.

The four facts that end the second day

Nuno's error came from a version. The library he used had published a new release between his install and his colleague's, and a function had moved. The mechanism that prevents this is written down in three characters of punctuation.

Semantic versioning: 2.4.1 means major 2, minor 4, patch 1. The rule the publisher promises to follow is that a patch fixes something, a minor adds something without breaking what was there, and a major is allowed to break. So the number is not decoration. It is a claim about what will happen to your code if you accept it.

What a project file usually records is a range rather than a version, and the punctuation carries the range. A caret in ^2.4.1 accepts 2.9.0 and refuses 3.0.0. That is the setting Nuno had, and it is the right default: it takes fixes and stays on the side of the promise. It also means that installing on a Tuesday and installing on a Thursday can genuinely give two different libraries, which is exactly what happened.

The resolution is the second file, the one nobody reads. A lockfile pins the exact versions so a second machine gets the same ones. It is generated rather than written, it belongs in the repository, and its whole job is to record the resolution of every range at one moment so that it can be reproduced. Nuno's project did not have one committed. With uv.lock in the repository his colleague's install would have been the same install, and the second day would have been an hour.

  • uv add pandas writes the range into the project file and the exact version into the lockfile
  • uv sync on the second machine installs from the lockfile rather than resolving again
  • git add uv.lock is the step that was missing

How much the language does for you, and how much you assemble

Languages differ enormously in how much arrives in the box, and the difference is countable. Writing the same trivial server that returns one line of text, a Go project installs 0 packages, because the server is in the standard library. The Rust version installs 4 and the Python version installs 2. The JavaScript version installs 49, which is why the only language a browser runs is also the one with the largest package registry and the deepest dependency trees. Neither is better, and the difference shows up as a number Nuno had never looked at. His invoice tool declares four dependencies and installs thirty one, because those four bring their own, and uv tree is the command that prints the whole thing on one screen. A Go version of the same tool would have declared none. The four are not carelessness; they are the reading of a spreadsheet format he was never going to write himself.

This is the axis worth checking before starting a language rather than after. It predicts what the first week feels like, what the tooling arguments will be about, and how much of the code you ship will be code somebody else wrote.

One hello world server

How many packages a language has to be given before it can answer a request

The same trivial program in four languages, counting everything installed rather than everything declared.

Direct and indirect packages installed for a server that returns one line of text. Counted here, on one afternoon, and indicative rather than official.

  1. JavaScriptAlmost nothing ships, and the registry supplies the rest49
  2. RustCargo is official; the web half is not4
  3. PythonA large standard library, and a small framework on top of it2
  4. GoThe server is in the standard library, and the project declares nothing0

A high count is not a weakness. An ecosystem that supplies everything moves faster than a committee, and the price is paid in dependency trees rather than in waiting for a language release.

Where people stall

The stall is the second language, and it is caused by the first one going well. Somebody learns readable, everywhere, and the usual first language, gets comfortable, and concludes that the comfort was fluency in programming. Then they open memory safety without a garbage collector and meet a compiler that refuses code they cannot see anything wrong with, and read the refusal as a verdict on themselves. It is not. The first language taught syntax and one ecosystem; the second one is charging for the assumptions the first one let them keep.

The other stall is smaller and more common. People try to learn a language by reading about the language, and never install anything, because the install is the boring part. The install is the subject.

What to open, in 2026

For Python, uv is the current answer and it replaced a stack of three: pip for installing, virtualenv for isolation and pyenv for runtime versions. It does all three, it is fast enough that the speed is a genuine feature, and it writes the lockfile Nuno needed. For JavaScript, npm ships with Node and remains the default, having taken over from copying a folder of libraries into the project by hand. For Rust, cargo arrives with the language and there has never been a second option, which is the quiet argument for the languages that decided this early.

A coding assistant will write correct code in a language you have never opened, and it is at its most confident exactly where the ecosystem differs from the one you know. It will not tell you that the lockfile was not committed.

What none of this decides is which of the twelve below to spend the next six months on, and the honest answer depends on where the code has to run rather than on the language, which is the argument made across the rest of programming, from the first variable to a deployed system.

Alongside this

Above this