Both are mature, actively maintained, and used for serious production contracts. The real difference is what language your tests and scripts are written in, and that follows from who is on your team and what else your project contains.
Short answer: if your team is mostly Solidity developers and the contracts are the product, start with Foundry. If the contracts sit inside a TypeScript codebase (a dApp frontend, a backend, deployment pipelines in Node), Hardhat fits more naturally. Plenty of teams use both.
What each one is
Foundry is a set of command-line tools written in Rust:
forge: build, test, fuzz, measure coverage and gas, and run deployment scripts;cast: talk to any chain from the shell (send transactions, call functions, decode data);anvil: a local node, with optional mainnet forking;chisel: a Solidity REPL.
Tests and deployment scripts are Solidity contracts. Dependencies are usually git repositories installed with forge install.
Hardhat is a Node.js framework from the Nomic Foundation. It includes a local development network, a task runner and a large plugin ecosystem (contract verification, deployments with Hardhat Ignition, coverage, gas reporting). Tests and scripts are traditionally written in TypeScript or JavaScript with ethers or viem, and dependencies come from npm.
The same test in both
Foundry, in Solidity (against the Counter contract that forge init generates):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, stdError} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
Counter counter;
function setUp() public {
counter = new Counter();
}
// Any function with parameters is a fuzz test: forge runs it with many random inputs.
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
function test_IncrementOverflowReverts() public {
counter.setNumber(type(uint256).max);
vm.expectRevert(stdError.arithmeticError); // cheatcode: next call must revert
counter.increment();
}
}