skip to content

In bash, what does declaring a variable with `declare -i` change about later assignments to it, and how does that compare with a plain assignment using `$(( ))`?

level: middleimportance: should knowfreq 30%

answer

  1. an attribute on the variable, not the value
  2. assignment becomes evaluation
  3. bad text turns into zero
  4. action at a distance from the declaration
  5. three spellings, one evaluator

basics

~20 s

declare -i marks a variable as integer, so every later assignment to it is evaluated as an arithmetic expression rather than stored as text. Non-numeric input silently becomes 0, and += means addition instead of string concatenation. A plain assignment with $(( )) makes that evaluation visible at each use site.

solid answer

~50 s

`declare -i count` sets the integer attribute, which changes assignment semantics for the rest of the variable's life: `count=3+4` stores 7, `count+=1` adds rather than appends, and `count=abc` stores 0, because in arithmetic context an unset name evaluates to zero. That last part is the reason many teams avoid the attribute — a typo or an empty value becomes a plausible-looking 0 instead of an error, and the surprising behaviour is declared far away from the line that reads oddly. `count=$(( count + 1 ))` says the same thing locally and needs no context to read. `let` is the third spelling of the same evaluator: `let 'x = a * 2'` evaluates arithmetically and returns 1 when the last expression evaluates to zero, exactly as `(( ))` does, but it needs quoting to survive spaces and globs, so `(( ))` is the modern recommendation.

code

bash · 8 lines
bash
#!/usr/bin/env bash
declare -i count=0
count=3+4        # arithmetic on assignment -> 7
count+=1         # addition, not concatenation -> 8
declare -i bad
bad=nonsense     # unset name in arithmetic context -> 0, silently
plain=3+4        # ordinary variable keeps the text
echo "$count $bad $plain"   # 8 0 3+4

go deeper

for a junior

Know that declare -i n makes n an integer variable, so n=3+4 stores 7 instead of the text. Remember that plain variables store whatever string you assign.

for a middle

Explain that the attribute changes assignment semantics only, that += becomes addition, and that an unrecognised word evaluates to 0. Compare it with writing $(( )) at each use site.

for a senior

Argue the maintainability case: action at a distance, silent zeros from external input, and why explicit arithmetic plus an input-shape check is the safer default in scripts other people operate.

for a principal

Own the style position — whether the attribute is permitted at all in your codebase, where numeric input gets validated at the boundary, and how you keep older let-based scripts consistent as they are touched.

## The integer attribute `declare -i name` (or `typeset -i`, its ksh-compatible alias) sets the *integer attribute* on a variable. The attribute does not change how the variable is read; it changes how it is **written**. Once set, the right-hand side of every assignment is passed through the arithmetic evaluator rather than stored verbatim. ```bash declare -i n n=3+4 # stores 7 n="2 * 5" # stores 10 -- quoting keeps it one word, arithmetic does the rest n+=1 # 11 -- += is addition, not string concatenation n=n*2 # 22 -- bare names are resolved, as in any arithmetic context ``` Contrast with a plain variable, where the same assignments store text: ```bash m=3+4 # stores the four-character string "3+4" m+=1 # stores "3+41" ``` ## The failure mode that makes people avoid it Arithmetic context treats an unset or null name as 0. Combined with the integer attribute, that turns bad input into a believable number: ```bash declare -i retries retries="$user_supplied" # "three" -> 0, "" -> 0, "5abc" -> arithmetic error ``` A value of `abc` is parsed as a variable name, found unset, and becomes 0. There is no warning. Downstream, `if (( retries > 0 ))` quietly takes the wrong branch. The attribute is also *invisible at the point of confusion*: someone reading `retries="$input"` two hundred lines below the `declare` has no local signal that an evaluation is happening. The declaration is action at a distance. A second sharp edge is that the attribute is a property of the variable, not of the value, and it persists — including for exported variables within the same shell, and for anything the same name is reused for later in a long script. ## `let`, `(( ))` and `declare -i` are three doors to one evaluator ```bash let 'total = price * qty' (( total = price * qty )) total=$(( price * qty )) ``` All three compute the same thing. The differences are ergonomic and in what they return: - **`let`** takes each argument as a separate expression and returns 1 if the *last* one evaluates to zero — the same inverted status rule as `(( ))`, and the same trap for `let count++` at zero. Its real problem is quoting: `let x = 1 + 1` fails because the shell word-splits it, and `let x=a*2` can be mangled by pathname expansion when a matching file exists, so nearly every use needs quotes. `let` predates `(( ))` and survives mainly in older scripts. - **`(( ))`** needs no quoting because no word splitting or globbing happens inside it, which makes it the readable modern form for both conditions and assignments. - **`$(( ))`** is the expansion, so its status is that of the surrounding command — the reason it is the safest form of an increment in a `set -e` script. ## Where `declare -i` still earns its place It is genuinely useful for a small, tightly scoped accumulator whose whole life is visible on one screen — a loop counter inside a short function, for example, where `total+=size` reads better than repeating `total=$(( total + size ))`. It also documents intent: the declaration states that this variable is a number. As a general habit, though, most style guides prefer explicit `$(( ))` at each site. It is local, greppable, and it does not turn a bad string into a silent zero. If you do use the attribute on anything touching external input, validate the value first — a `[[ $input =~ ^[0-9]+$ ]]` guard, or a `case` on the shape — rather than relying on arithmetic to reject it, because arithmetic will not. ## Related `declare` options worth naming `declare -r` makes a variable readonly, and `declare -A` creates an associative array (bash 4 and later). Both are attributes set the same way and can be combined, as in `declare -ir MAX=10` for a readonly integer. Attribute flags are cleared with a leading `+`, so `declare +i n` removes the integer attribute and restores ordinary string assignment.

  • Why would a reviewer push back on `declare -i` for a variable that holds user or config input?
    Because arithmetic context turns an unrecognised word into 0 rather than raising an error, so `retries="three"` silently becomes 0 and a later `(( retries > 0 ))` takes the wrong branch. The declaration is also far from the assignment, so the surprising line looks like a normal string assignment. Validate the input explicitly instead.
  • What does `+=` do differently on a variable declared with `declare -i`?
    On an ordinary variable `+=` concatenates text, so `m=3; m+=1` gives the string 31. With the integer attribute the right-hand side is evaluated arithmetically and added, so `n=3; n+=1` gives 4. It is the same operator producing two different results depending on an attribute set elsewhere.
  • Why is `(( ))` preferred over `let` in modern scripts?
    They share one evaluator, but `let` takes shell words, so spaces need quoting and an unquoted `let x=a*2` can be altered by pathname expansion when a matching file exists. Inside `(( ))` there is no word splitting or globbing at all, so it reads cleanly without quotes. `let` also carries the same zero-value exit-status trap.
  • How do you remove the integer attribute once it has been set?
    Use a leading plus instead of a minus: `declare +i n` clears the attribute and restores ordinary string assignment, leaving the current value in place as text. The same `+` form works for other attribute flags. Needing it is usually a sign the attribute was the wrong tool for that variable.

saying these in an interview costs you the question

  • Thinking declare -i validates or rejects non-numeric input
  • Believing the attribute affects reads rather than assignments
  • Expecting += to concatenate on an integer variable
  • Treating let and (( )) as different evaluators
  • Assuming the attribute makes arithmetic support decimals

context