<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ILearn]]></title><description><![CDATA[This is a blog where I share what I learn in simpler terms, with the hope of someone understanding it better]]></description><link>https://inengiyeemmanuel.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 06 Sep 2026 09:58:09 GMT</lastBuildDate><atom:link href="https://inengiyeemmanuel.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What I learned building a BitTorrent client from scratch.]]></title><description><![CDATA[Most people have never thought about what happens when a file is downloaded. You click you wait, you get the file, but what if there was no single server to download from? what if the file came from a]]></description><link>https://inengiyeemmanuel.hashnode.dev/what-i-learned-building-a-bittorrent-client-from-scratch</link><guid isPermaLink="true">https://inengiyeemmanuel.hashnode.dev/what-i-learned-building-a-bittorrent-client-from-scratch</guid><category><![CDATA[Go Language]]></category><category><![CDATA[backend]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming concepts]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Emmanuel Inengiye]]></dc:creator><pubDate>Thu, 26 Mar 2026 11:16:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69bbd7538c55d6eefbd3edef/ef61db6c-ef7d-44d4-acc3-9da081746fcf.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most people have never thought about what happens when a file is downloaded. You click you wait, you get the file, but what if there was no single server to download from? what if the file came from a number of strangers’ computers simultaneously, each of them sending you a tiny piece. That’s what BitTorrent is, I came across it in a repo (build-your-own-x by code-crafters-io) while looking for a low-level project to build and I learnt a lot while building it.</p>
<p>I’ll be taking you on what I learnt, and how I went about building it.</p>
<h2>What is BitTorrent?</h2>
<p>BitTorrent is peer-to-peer file sharing protocol. A peer simply means a computer in the same torrent network, so BitTorrent protocol essentially allows computers in the same torrent network share files, instead of downloading from a single server. For this to be possible there are different sections which includes:</p>
<ol>
<li><p>a .torrent file: this is a small metadata file that contains file name, size, tracker URL and hashes for every piece. This is the data my BitTorrent client uses to verify my data is not corrupted.</p>
</li>
<li><p>Tracker: this is a central server that keeps a list of everyone currently downloading or seeding a file, it stores every peer IP address.</p>
</li>
<li><p>Peers: these are other computers downloading or uploading the same file from each other and to each other. They include Seeders(upload pieces) and Leechers (download pieces)</p>
</li>
<li><p>Pieces: The file is split into fixed-size chunks. Each piece has a hash in the .torrent file. When my client receives a piece it verifies the hash to check if it is corrupted or tampered with, if it is, it is discarded or re-requested.</p>
</li>
<li><p>Bitfield: this is a map that shows which pieces a peer has. it is just a string of bytes. 1 represents “this piece is present”, 0 represents “this piece is absent”</p>
</li>
</ol>
<img src="https://cdn-images-1.medium.com/max/800/1*NCaKZkWYG-BpjM9hLclMlA.png" alt="BITFIELD" style="display:block;margin:0 auto" />

<p>6. Bencode: this is the encoding format for the .torrent file. it is analogous to JSON.</p>
<p>The process is:</p>
<ol>
<li><p>open the .torrent file</p>
</li>
<li><p>client contacts the tracker using the tracker URL</p>
</li>
<li><p>tracker returns a list of peers</p>
</li>
<li><p>connect to those peers and exchange bitfields</p>
</li>
<li><p>client requests specific pieces from peers who have them</p>
</li>
<li><p>pieces arrive, get verified with hash and then the bitfield updates as you accumulate them</p>
</li>
<li><p>client simultaneously uploads pieces to other peers, as it is downloading</p>
</li>
<li><p>once complete, the download is done!</p>
</li>
</ol>
<h2>What I had to learn Before Writing a Single line</h2>
<p>This wasn’t a project I could dive straight into. It operates at a low-level that I had to genuinely understand the protocols underneath. Here are the things I picked up:</p>
<h3>TCP Connections</h3>
<p>Everything in BitTorrent runs over TCP. A TCP connection is a reliable, two-way communication channel between 2 computers. Before there is exchange of data, both computers perform a handshake.</p>
<ol>
<li><p>Computer A sends a SYN</p>
</li>
<li><p>Computer B sends a SYN-ACK</p>
</li>
<li><p>Computer A sends an ACK</p>
</li>
</ol>
<p>Once connected data flows as packets. Each packet is acknowledged on arrival and every packet has a sequence number so they can be reassembled in order upon delivery.</p>
<p>In Go, setting this up was straight forward, I used the Dial method of the net package. This readily implements an io.Reader which made it easier to read.</p>
<img src="https://cdn-images-1.medium.com/max/800/1*xxw48QOEjK1eoyPEYrj6kw.png" alt="TCP CONNECTION" style="display:block;margin:0 auto" />

<h3>Concurrency</h3>
<p>This was one of the harder concepts for me to learn, it is used very much in BitTorrent and networking generally. It means managing multiple tasks that are in progress at the same time. Some of the few terms associated are:</p>
<p>Concurrency — Juggling multiple tasks by switching between them rapidly</p>
<p>Parallelism — tasks executing at the same time across multiple CPU cores</p>
<p>Thread — this is the smallest unit of execution, threads within the same process share memory. They are the building blocks of processes</p>
<p>Process — a heavier isolated unit with its own memory space; processes contain threads</p>
<p>Go was particularly built for concurrency, Go gives you goroutines instead of normal OS threads. goroutines are ultra-lightweight threads managed by the Go runtime. goroutines communicate with each other using channels, passing data between themselves.</p>
<pre><code class="language-go">go doSomething() // spawn a goroutine
ch &lt;- value // send to channel
value := &lt;- ch // recieve from channel
</code></pre>
<p>The sync package which I came across, handles the rest like mutexes, wait groups and anything you need to coordinate goroutines.</p>
<h3>Big-Endian Encoding</h3>
<p>When computers send raw bytes over a network, byte order matters. Big-endian means the most significant byte comes first. This is similar to the way humans write numbers. For example; 1,024 is written as 1–0–2–4, most significant digit first.</p>
<p>Go uses binary.BigEndian from the encoding/binary package, which writes values directly into a buffer in the right order.</p>
<pre><code class="language-go"> var b bytes.Buffer
 binary.Write(buffer, binary.BigEndian, uint64(num))
</code></pre>
<h2>The Code</h2>
<p>After understanding those concepts, I structured the client into six packages. Here is what each one does</p>
<p>Bencode — This was the first package I wrote, A .torrent file isn’t JSON or plain text, Its encoded in its own format called bencode, with its own types and rules. I used an external dependency to handle the unmarshalling, which honestly made this the easiest package to build. It just opens the torrent file and fills a struct.</p>
<p>torrent — This takes the raw decoded data from the bencode package and transforms it into something the program can actually use. It calculates infoHash and splits the raw piece hash strings. it just tries to make raw data from the bencode file, understandable by my program.</p>
<p>tracker — Now I have the decoded torrent info, this package builds the tracker URL, and contacts the tracker server. This server responds with a list of peer IP addresses, the package gets the peers and also split them to individual peers.</p>
<p>peer — This is where things started feeling real. Every peer connection starts with a handshake, both sides confirming they’re sharing the same torrent. The first time I saw that handshake go through and two machines actually talk to each other, It felt genuinely good. Once connected, peers tell each other which pieces they have using bitfields.</p>
<p>bitfield— I already explained what a bitfield is earlier in the article, although the concept didn’t click immediately, I had to ask a lot of questions to an LLM before I could understand. This package has 2 functions, it checks whether the piece is present, if it isn’t there is a setPiece function to help with that.</p>
<p>download — This took the longest and was the hardest to write. It has five functions: worker, downloadPiece, downloadFromPeer, download and doHandshakeFlow. I had to refactor this file a couple of times for me to successfully complete a download. It gave me a real feel for concepts like pipelining and using workers in your codebase.</p>
<p>main — the main function ties everything together. It reads the torrent file, spins up the tracker request, manages all peer connections concurrently and writes verified pieces to disk as they complete.</p>
<pre><code class="language-plaintext">bencode → torrent → tracker → peer → bitfield → download
</code></pre>
<h2>The Bug that Taught me more than the Docs did</h2>
<p>At some point the client just stalled. Certain peers would connect fine, the handshake would go through, and then nothing. No pieces, No errors, just silence</p>
<p>I was expecting every peer to send a bitfield message (ID=5) as their first message after the handshake (a map of all the pieces they have). But some peers were skipping it entirely and jumping straight to an unchoke message (ID=1).</p>
<p>Turns out this is valid behavior. A peer that has every single piece of the file sometimes skips the bitfield altogether and goes straight to unchoke. Why send a map of what you have when you have everything?</p>
<p>The fix was to check the first message ID, and if its an unchoke instead of a bitfield, just assume the peer has all the pieces and construct a bitfield manually</p>
<pre><code class="language-go">var bf bitfield.Bitfield
var alreadyUnchoked bool
switch msg.ID {
case peer.MsgBitfield:
  fmt.Println("got bitfield")
  bf = bitfield.Bitfield(msg.Payload)
case peer.MsgUnchoke:
  fmt.Println("peer sent unchoke instead of bitfield, assuming all pieces available")
  bf = make(bitfield.Bitfield, len(t.PieceHashes)/8+1)
  for i := range bf {
   bf[i] = 0xff
  }
  alreadyUnchoked = true
default:
 return fmt.Errorf("unexpected message ID: %d", msg.ID)
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69bbd7538c55d6eefbd3edef/7d61759a-bdd9-42b8-8ced-e7eb53ad76c5.jpg" alt="" style="display:block;margin:0 auto" />

<h2>What I Actually Came Away With</h2>
<p>I came away understanding some foundational concepts, They are no longer abstract anymore. I understand the entire flow of the codebase and every part of it. I understand the entire flow of the codebase and every part of it. I understand how the foundational concepts play a big role in the mechanics of such a project like this.</p>
<p>I am also more honest now about what "learning" looks like in 2026, with the presence of AI and LLMs. It's faster than it used to be. It's messier. Sometimes you understand something deeply, sometimes you absorb it just enough to keep moving. The goal, as far as I can tell, is to keep pushing until the absorbed stuff becomes the understood stuff.</p>
<p>Don't just stop at the answer, make sure you can explain it yourself as well.</p>
<h2>Final Thoughts</h2>
<p>Building this project made a lot of abstract concepts feel more real. Though I had a lot of assistance from LLMs and other resources. I still encountered several bugs that taught me important lessons. The process of fine-tuning the code in the later stages to achieve better performance and more consistent downloads showed me how much the decisions I make in my code truly matter.</p>
<p>Overall, it was a great project.</p>
<p>If you'd like to explore the implementation, you can check out the project here:</p>
<p>GitHub: <a href="https://github.com/Inengs/bittorrent-client">https://github.com/Inengs/bittorrent-client</a></p>
<p>I'm always open to feedback, suggestions, or conversations with other builders.</p>
]]></content:encoded></item></channel></rss>