Six classic algebra problems, each solved the standard way by hand and then again with a short Go program — so you learn the math and the code together.
Named after physicist Richard Feynman: if you can't explain an idea in plain words, you don't fully understand it yet. For each problem, work these four steps:
2.0 instead of 2. That forces Go to use floating-point math — otherwise 7 / 2 would be 3 (integer division), not 3.5, and your algebra answers would be wrong. The %g verb in Printf prints a number without trailing zeros, so 4.0 shows as 4.The goal of algebra is almost always the same: get the unknown letter alone on one side of the =. Whatever x is, it must make the left side equal 11.
We rewrite the equation in the general form ax + b = c, then the by-hand steps become one line: x = (c - b) / a. The program also checks its answer by plugging x back in.
package main
import "fmt"
func main() {
// 2x + 3 = 11 → ax + b = c
a, b, c := 2.0, 3.0, 11.0
x := (c - b) / a
fmt.Printf("x = %g\n", x)
fmt.Printf("check: 2*%g + 3 = %g\n", x, a*x+b)
}
x = 4 check: 2*4 + 3 = 11
Why are you allowed to "subtract 3 from both sides" — why doesn't that change the answer?
Think of the = as a balance scale that's perfectly level. If you take the same weight off both pans, it stays level. Subtracting 3 from each side keeps the two sides equal, so the value of x that balanced it before still balances it now — you've just made it easier to read.
Now x appears on both sides. The plan: gather all the x terms on one side and all the plain numbers on the other, then finish like Problem 1.
In the general form ax + b = cx + d, moving terms gives (a − c)x = d − b, so x = (d - b) / (a - c).
package main
import "fmt"
func main() {
// 5x - 4 = 3x + 8 → ax + b = cx + d
a, b, c, d := 5.0, -4.0, 3.0, 8.0
x := (d - b) / (a - c)
fmt.Printf("x = %g\n", x)
fmt.Printf("check: LHS = %g, RHS = %g\n", a*x+b, c*x+d)
}
x = 6 check: LHS = 26, RHS = 26
Why do we bother moving all the x terms to one side and all the numbers to the other?
Because you can only divide out x once it's alone. Gathering like things together turns a messy equation into the tidy shape (a number) · x = (a number). From there, one division sets x free. The check confirms it: both sides equal 26 when x = 6.
A fraction just means "divided by." Undo the division first, then solve the simple equation that's left.
In the form (x + n) / d = r, the two steps collapse to x = r*d - n.
package main
import "fmt"
func main() {
// (x + 2) / 4 = 5 → (x + n) / d = r
n, d, r := 2.0, 4.0, 5.0
x := r*d - n
fmt.Printf("x = %g\n", x)
fmt.Printf("check: (%g + 2)/4 = %g\n", x, (x+n)/d)
}
x = 18 check: (18 + 2)/4 = 5
Why does multiplying both sides by 4 make the fraction disappear?
Multiplying is the exact opposite of dividing. The left side was divided by 4; multiplying it by 4 undoes that and leaves it whole. You have to do the same to the right side to keep the balance, which turns the 5 into 20. Inverse operations always cancel each other.
Two unknowns, two equations. The elimination method cancels one variable so you can solve for the other, then back-substitutes.
For any system a₁x + b₁y = c₁ and a₂x + b₂y = c₂, there's a general formula (Cramer's rule). It's just the elimination method written once for all cases.
package main
import "fmt"
func main() {
// 2x + y = 8
// x - y = 1
a1, b1, c1 := 2.0, 1.0, 8.0
a2, b2, c2 := 1.0, -1.0, 1.0
det := a1*b2 - a2*b1
x := (c1*b2 - c2*b1) / det
y := (a1*c2 - a2*c1) / det
fmt.Printf("x = %g, y = %g\n", x, y)
fmt.Printf("check: 2x+y = %g, x-y = %g\n", 2*x+y, x-y)
}
x = 3, y = 2 check: 2x+y = 8, x-y = 1
When you add the two equations, why does the y vanish?
One equation has +y and the other has −y. Adding them stacks +y + (−y), which is 0 — the y cancels itself out. That's the whole trick of elimination: line the equations up so one variable adds to zero, leaving a single equation you can solve.
The x² makes this a quadratic. It can have two solutions. You can often factor it; the quadratic formula always works.
For ax² + bx + c = 0, the quadratic formula is x = (−b ± √(b² − 4ac)) / 2a. The ± is why there are two answers. Go's math.Sqrt handles the square root.
package main
import (
"fmt"
"math"
)
func main() {
// x^2 - 5x + 6 = 0 → ax^2 + bx + c = 0
a, b, c := 1.0, -5.0, 6.0
disc := b*b - 4*a*c // the discriminant
root := math.Sqrt(disc)
x1 := (-b + root) / (2 * a)
x2 := (-b - root) / (2 * a)
fmt.Printf("discriminant = %g\n", disc)
fmt.Printf("x = %g or x = %g\n", x1, x2)
}
discriminant = 1 x = 3 or x = 2
The part under the square root, b² − 4ac, is called the discriminant. What can it tell you before you finish solving?
Its sign counts the real answers. Positive (like our 1) means two different solutions; exactly 0 means one repeated solution; negative means no real solutions at all (you can't take the real square root of a negative). So a quick glance at the discriminant tells you what to expect before you do the rest.
Two points fix a straight line. We want it in the familiar form y = mx + b, where m is the slope and b is where it crosses the y-axis.
The two hand steps are two lines of Go. The small if at the end just prints a tidy − 1 instead of + -1.
package main
import "fmt"
func main() {
// line through (1, 2) and (3, 8)
x1, y1 := 1.0, 2.0
x2, y2 := 3.0, 8.0
m := (y2 - y1) / (x2 - x1)
b := y1 - m*x1
sign, bb := "+", b
if b < 0 {
sign, bb = "-", -b
}
fmt.Printf("slope m = %g\n", m)
fmt.Printf("y = %gx %s %g\n", m, sign, bb)
}
slope m = 3 y = 3x - 1
Why is the slope "rise over run," and what does b actually represent on the graph?
Slope measures steepness: how far the line climbs (rise, the change in y) for each step it moves sideways (run, the change in x). A slope of 3 means "up 3 for every 1 across." And b is the line's height when x = 0 — the exact spot where it crosses the vertical y-axis.