Build the mental model
Bash treats every variable as a string by default, which is why arithmetic needs its own dedicated contexts rather than working automatically the way it does in languages with native number types. $(( expression )) evaluates an arithmetic expression and substitutes the resulting number, so you can capture it in a variable or print it directly, as with $((a + b)) or $((a ** 2)) for exponentiation. (( expression )) does the same underlying evaluation but discards the number and instead sets the command's exit status based on whether the result is nonzero (true) or zero (false), which is exactly what if and while need — it is also how compound assignment shortcuts like ((a += 5)) work, mutating a variable in place. The older let command, as in let "b *= 2", predates this dedicated form of (( )) and still works, but is rarely used in new scripts because the (( )) syntax is clearer and more consistent. The trickiest part for beginners is that [[ ]] supports two entirely different kinds of comparison operator depending on what you're comparing: -eq, -ne, -lt, -le, -gt, -ge for numbers, and ==, != for strings — and bash will never warn you if you pick the wrong family. It simply performs whichever comparison you asked for, numeric or lexicographic, even when the result looks like nonsense for what you actually meant.
Connect it to a real scenario
Picture a script that reads a numeric threshold from a config file as a string and compares it against a count of failed jobs to decide whether to send an alert — this lesson's code demonstrates exactly how easy it is to get this wrong. With x="10" and y="9", the numeric comparison [[ "$x" -gt "$y" ]] correctly reports that x is greater, because -gt forces bash to interpret both operands as integers before comparing. But [[ "$x" > "$y" ]] using the string operator > compares them character by character instead: it looks at the first character of each, '1' versus '9', and since '1' sorts before '9' lexicographically, it concludes x is NOT greater — the opposite of the numeric truth. This is precisely the kind of bug that breaks alerting logic silently: a threshold of 9 could look 'greater than' a count of 10 with no error message at all, because both are perfectly valid strings as far as bash is concerned. The lesson's code sidesteps this entirely with (( a > b )) for arithmetic evaluated inside an if, and -gt inside [[ ]] whenever numbers are being compared — the discipline is to always ask yourself whether you're comparing quantities or comparing text, and pick the matching operator family every time, since bash itself won't catch the mistake for you.
Try the working example
#!/bin/bash
a=10
b=3
echo "Sum: $((a + b))"
echo "Quotient: $((a / b))"
echo "Remainder: $((a % b))"
echo "Power: $((a ** 2))"
((a += 5))
echo "a after += 5: $a"
let "b *= 2"
echo "b after *= 2: $b"
if (( a > b )); then
echo "a is numerically greater than b"
fi
x="10"
y="9"
if [[ "$x" -gt "$y" ]]; then
echo "Numeric compare: x is greater than y"
fi
if [[ "$x" > "$y" ]]; then
echo "String compare: x is greater than y"
else
echo "String compare: x is NOT greater than y"
fiRunning this script prints:
Sum: 13
Quotient: 3
Remainder: 1
Power: 100
a after += 5: 15
b after *= 2: 6
a is numerically greater than b
Numeric compare: x is greater than y
String compare: x is NOT greater than y5-minute try-it
Write a small script that stores two numbers as strings, e.g. "20" and "9", and prints whether the first is greater than the second using both a numeric comparison and a string comparison, so you can see the results disagree.
One important caution
Using == inside [[ ]] to compare two numbers, which works as a string comparison and gives wrong answers for multi-digit numbers like "9" vs "10"
Using -eq, -lt, etc. on a variable that isn't a valid integer, which causes bash to throw an 'integer expression expected' error at runtime
GNU Bash Manual: Shell Arithmetic — Bash / Shell Scripting