Learn How to Build a Simple Blockchain in Python: Absolute Beginners Tutorial

A blockchain is one of those ideas that sounds intimidating until you actually write one. Strip away the cryptocurrency hype and what you have is a linked list where each node stores a cryptographic fingerprint of the one before it. Change any record, and every fingerprint downstream breaks. Python gives you everything you need to build that structure in under 100 lines of code.

This tutorial requires no prior blockchain knowledge. You need a basic understanding of Python classes and methods. By the end, you will have a working blockchain that can store data, chain blocks together cryptographically, enforce proof of work, and detect tampering.

What a Blockchain Actually Is

Picture a traditional spreadsheet row. You can open it, change a number, and save it. Nobody outside the file knows you made a change. A blockchain is built to prevent exactly that. Each block in the chain contains data, a timestamp, and a hash — a fixed-length string produced by running the block's contents through a mathematical function. That hash is then stored inside the next block as its previous_hash.

If you alter block 3, its hash changes. Block 4 stores the old hash of block 3 as its previous_hash. Now block 4's recorded previous_hash no longer matches the actual hash of block 3. The chain is broken. You would need to recompute every block from 3 onward — and, in real networks, outpace thousands of other computers doing the same thing simultaneously. That is where proof of work comes in.

Note

Python's standard library includes hashlib, which exposes SHA-256 and other cryptographic hash functions. SHA-256 takes any input and returns a deterministic 64-character hexadecimal string. The same input always produces the same output; any change to the input produces a completely different output. No installation required — import hashlib is all you need.

The three core properties

Before writing any code, it helps to have the three structural properties of a blockchain clear in your head.

What it means
Once a block is added to the chain, altering its data invalidates its hash and breaks every subsequent block's previous_hash reference.
How Python enforces it
The compute_hash method re-derives the hash on demand. The is_valid method compares stored hashes against freshly computed ones.
What it means
Each block stores the hash of the previous block, creating a linked sequence. The first block (the genesis block) has no predecessor, so its previous_hash is set to a string of zeros.
How Python enforces it
The Blockchain.add_block method reads self.chain[-1].hash and passes it to the new Block constructor.
What it means
Adding a block requires finding a nonce — an integer — such that the resulting hash starts with a fixed number of leading zeros. Higher difficulty means more leading zeros and more computation.
How Python enforces it
The mine_block method loops, incrementing self.nonce and recomputing the hash each iteration until the hash starts with the required number of zero characters.

Building the Block Class

Start with the Block class. Each block needs an index, a timestamp, some data, the hash of the previous block, a nonce for proof of work, and its own hash. Import hashlib and datetime at the top of your file.

python
import hashlib
import datetime


class Block:
    def __init__(self, index, data, previous_hash):
        self.index         = index
        self.timestamp     = str(datetime.datetime.utcnow())
        self.data          = data
        self.previous_hash = previous_hash
        self.nonce         = 0
        self.hash          = self.compute_hash()

    def compute_hash(self):
        block_string = (
            str(self.index)
            + self.timestamp
            + str(self.data)
            + self.previous_hash
            + str(self.nonce)
        )
        return hashlib.sha256(block_string.encode()).hexdigest()

The compute_hash method concatenates every field into a single string, encodes it to bytes, and passes it through SHA-256. The result is a 64-character hex string like 00002f4a.... Notice that self.hash is assigned in __init__ right after the nonce is set to zero. Once proof of work runs, the nonce will change, so the hash gets recomputed then too.

Pro Tip

Always include the nonce in the string you hash. If you forget it, the proof-of-work loop will compute the same hash forever because nothing in the input ever changes.

Adding proof of work

Proof of work is a mine_block method on Block. It accepts a difficulty integer and keeps incrementing the nonce until the hash starts with that many zeros.

python
    def mine_block(self, difficulty):
        target = "0" * difficulty
        while not self.hash.startswith(target):
            self.nonce += 1
            self.hash = self.compute_hash()
        print(f"Block {self.index} mined. Nonce: {self.nonce}  Hash: {self.hash}")

At difficulty 4, the miner hunts for a hash that starts with 0000. On average, that takes thousands of iterations. Increase difficulty to 5 and you are looking for 00000, which multiplies the expected work by 16. For this tutorial, keep difficulty at 2 or 3 so the code runs quickly.

code builder click a token to place it

Build the correct line that computes a SHA-256 hash from a string variable called block_string and returns its hex digest:

your code will appear here...
hashlib.sha256( .hexdigest() return block_string.encode() ).hexdigest() .encode().hexdigest()
Why: The correct order is return hashlib.sha256(block_string.encode()).hexdigest(). You must first encode the string to bytes before passing it to sha256, then call hexdigest() on the resulting hash object to get the hex string. The closing parenthesis of sha256( comes after the encoded bytes, not before .hexdigest().

Building the Blockchain Class and Validating the Chain

The Blockchain class is a thin wrapper around a list of blocks. It creates the genesis block automatically and provides methods for adding new blocks and verifying the chain's integrity.

python
class Blockchain:
    DIFFICULTY = 3

    def __init__(self):
        self.chain = []
        self._create_genesis_block()

    def _create_genesis_block(self):
        genesis = Block(0, "Genesis Block", "0" * 64)
        genesis.mine_block(self.DIFFICULTY)
        self.chain.append(genesis)

    def add_block(self, data):
        last_block = self.chain[-1]
        new_block  = Block(len(self.chain), data, last_block.hash)
        new_block.mine_block(self.DIFFICULTY)
        self.chain.append(new_block)

    def is_valid(self):
        for i in range(1, len(self.chain)):
            current  = self.chain[i]
            previous = self.chain[i - 1]

            # Recompute the current block's hash
            if current.hash != current.compute_hash():
                print(f"Block {i} hash mismatch — data has been altered.")
                return False

            # Confirm the linkage
            if current.previous_hash != previous.hash:
                print(f"Block {i} previous_hash mismatch — chain is broken.")
                return False

        return True

The genesis block uses "0" * 64 as its previous_hash — a string of 64 zeros that signals there is no real predecessor. Every other block reads self.chain[-1].hash to get the most recent block's hash before creating the new one.

"SHA-256 produces a 256-bit hash value." — Python Software Foundation, hashlib documentation

Running the complete example

Putting the two classes together, here is how you instantiate the blockchain, add blocks, verify the chain, and then demonstrate what happens when a block is tampered with.

python
# --- Run the blockchain ---
bc = Blockchain()
bc.add_block({"sender": "Alice", "receiver": "Bob", "amount": 50})
bc.add_block({"sender": "Bob",   "receiver": "Carol", "amount": 25})
bc.add_block({"sender": "Carol", "receiver": "Alice", "amount": 10})

print("\n--- Chain valid?", bc.is_valid())

# Tamper with block 1 and check again
bc.chain[1].data = {"sender": "Alice", "receiver": "Bob", "amount": 9999}
print("--- After tampering, chain valid?", bc.is_valid())

When you run this, you should see three mining messages, a True validity check, and then a mismatch error followed by False once the data in block 1 is changed. The validator caught the attack because it recomputed block 1's hash and found it no longer matched the stored value.

Watch Out

After modifying block.data in Python you can also call block.hash = block.compute_hash() to update the stored hash. The chain will still break because block 2's previous_hash still holds the old value. This is an important thing to test — it shows why linkage checking is a separate step from hash recomputation.

spot the bug click the line that contains the bug

The mine_block method below is supposed to keep incrementing the nonce until the hash starts with the required zeros. One line has a bug that makes the loop run forever. Click it.

1 def mine_block(self, difficulty):
2 target = "0" * difficulty
3 while not self.hash.startswith(target):
4 self.nonce = 0
5 self.hash = self.compute_hash()
6 print(f"Mined. Nonce: {self.nonce}")
The fix: Change self.nonce = 0 to self.nonce += 1. Resetting the nonce to zero inside the loop means the hash computation starts from the same input every iteration. The hash never changes, the condition never becomes true, and the loop never exits. Incrementing the nonce changes the input to compute_hash on each pass, producing a new hash each time until the target is met.
Three-block chain — each block's prev field stores the hash of the block to its left, creating the tamper-evident linkage.

How to Build a Simple Blockchain in Python

Follow these five steps to go from an empty file to a working, tamper-detectable blockchain. Each step builds directly on the last.

  1. Create the Block class

    Define a Block class whose __init__ method accepts index, data, and previous_hash. Set self.timestamp using datetime.datetime.utcnow(), initialize self.nonce = 0, and assign self.hash = self.compute_hash(). The compute_hash method concatenates all fields into a string, encodes it, and returns the SHA-256 hex digest.

  2. Add proof of work to the Block

    Write a mine_block(self, difficulty) method. Build a target string of difficulty zeros. Loop with while not self.hash.startswith(target), incrementing self.nonce and recomputing self.hash each iteration. Print the nonce and hash when the loop exits.

  3. Create the Blockchain class

    Define a Blockchain class with a class-level DIFFICULTY = 3 constant and an __init__ that creates an empty list and calls _create_genesis_block. That private method instantiates Block(0, "Genesis Block", "0" * 64), mines it, and appends it to self.chain. The add_block(self, data) method reads the last block's hash, creates a new block, mines it, and appends.

  4. Implement chain validation

    Write is_valid(self). Loop from index 1 to the end of the chain. For each block, call current.compute_hash() and compare the result to current.hash. Also compare current.previous_hash to previous.hash. Return False with a descriptive message on any mismatch, and return True only if all checks pass.

  5. Run and test the blockchain

    Instantiate Blockchain(), call add_block several times with dictionary data, and call is_valid() to confirm the chain is intact. Then modify bc.chain[1].data directly and call is_valid() again. The validator should report a hash mismatch on block 1 and return False, confirming that tamper detection works.

Python Learning Summary Points

  1. A blockchain is a list of blocks where each block stores the hash of its predecessor. This linkage makes retroactive alteration detectable because changing any block's contents changes its hash, which no longer matches the previous_hash stored in the following block.
  2. Python's built-in hashlib module provides SHA-256 hashing with no external dependencies. Pass a byte-encoded string to hashlib.sha256(...).hexdigest() to get a deterministic 64-character hex output.
  3. Proof of work adds a computational cost to block creation by requiring the hash to satisfy a difficulty target. The nonce is the only variable in the hash input that the miner controls, so the miner increments it until the hash meets the target.
  4. Chain validation requires two separate checks per block: that the block's stored hash still matches a freshly computed hash of its current contents, and that its previous_hash field still matches the actual hash of the preceding block.
  5. This tutorial is a conceptual model, not a production system. Real blockchains add peer-to-peer networking, consensus algorithms, digital signatures for transaction authorization, and many additional layers of security and reliability.

From here you can extend this foundation in several directions. Replace the data dictionary with a simple Transaction class. Add a list of pending transactions to the Blockchain that get bundled into the next mined block. Serialize the chain to JSON and reload it. Each of these extensions will reinforce the same concepts you used here — classes, hashing, iteration, and validation — while showing you how far a few Python primitives can take you.

check your understanding question 1 of 5

Frequently Asked Questions

A blockchain is a list of records called blocks, where each block stores data and a cryptographic hash of the previous block. This chain of hashes makes it extremely difficult to alter any past record without invalidating every block that follows it.

For a basic blockchain you only need two modules from Python's standard library: hashlib to compute SHA-256 hashes and datetime to timestamp each block. No third-party packages are required.

The genesis block is the first block in a blockchain. It has no previous block to reference, so its previous_hash field is typically set to a string of zeros or an empty string. Every subsequent block traces its lineage back to this starting point.

Proof of work requires a block's hash to start with a specific number of leading zeros. To meet this requirement, the miner must repeatedly increment a number called a nonce until the resulting hash satisfies the target. This computation takes real time and resources, making it costly to add blocks at will.

No. This tutorial builds a minimal blockchain to illustrate the core concepts: blocks, hashing, chaining, and proof of work. Real networks like Bitcoin add peer-to-peer networking, consensus rules, digital signatures, transaction validation, and many other layers on top of these fundamentals.

Loop through each block and check two things: that the block's stored hash matches a freshly computed hash of its contents, and that the previous_hash field matches the actual hash of the preceding block. If either check fails at any position, the chain has been altered.

A nonce is a number that miners increment during proof of work until the block's hash meets the required target. The word comes from "number used once." In this tutorial, nonce starts at zero and counts up until the SHA-256 hash of the block begins with the required number of leading zeros.

SHA-256 produces a fixed 64-character hexadecimal output regardless of input size, is deterministic, and is computationally infeasible to reverse. Changing even one character in the input produces a completely different hash, which is what makes the chain tamper-evident.