Numeric Types (Overview) — Interview Questions¶
Table of Contents¶
- Junior Level Questions
- Middle Level Questions
- Senior Level Questions
- Scenario-Based Questions
- FAQ — Common Interview Topics
Junior Level Questions¶
Q1: Name all integer types in Go.¶
Answer: - Signed: int8, int16, int32, int64, int - Unsigned: uint8, uint16, uint32, uint64, uint, uintptr - Aliases: byte (= uint8), rune (= int32)
Q2: What is the size of int in Go?¶
Answer: Platform-dependent. On 32-bit systems: 32 bits. On 64-bit systems: 64 bits. Never assume it's 64-bit.
import (
"fmt"
"strconv"
"unsafe"
)
fmt.Println(unsafe.Sizeof(int(0))) // 8 on 64-bit
fmt.Println(strconv.IntSize) // 64 on 64-bit
Q3: What are byte and rune?¶
Answer: - byte is an alias for uint8 (unsigned 8-bit integer, range 0-255) - rune is an alias for int32 (signed 32-bit integer, represents a Unicode code point)
Q4: What are the floating-point types in Go?¶
Answer: float32 (32-bit, ~7 significant digits) and float64 (64-bit, ~15-16 significant digits). The default is float64.
Q5: Does Go support implicit numeric type conversion?¶
Answer: No. All conversions must be explicit.
var a int32 = 10
var b int64 = 20
// c := a + b // COMPILE ERROR
c := int64(a) + b // OK: explicit conversion
Q6: What is the default type for integer literals?¶
Answer: int. For float literals: float64. For complex literals: complex128.
Q7: What happens when an integer overflows in Go?¶
Answer: It wraps around — no panic, no error. This is defined behavior.
var x uint8 = 255
x++
fmt.Println(x) // 0 (wrapped)
var y int8 = 127
y++
fmt.Println(y) // -128 (wrapped to min)
Q8: Why should you not use float64 for money?¶
Answer: Binary floating-point cannot represent all decimal fractions exactly. 0.1 + 0.2 != 0.3 in float64.
fmt.Println(0.1 + 0.2) // 0.30000000000000004
fmt.Println(0.1 + 0.2 == 0.3) // false
// Use int64 cents instead:
price := int64(10) // 10 cents
tax := int64(2) // 2 cents
total := price + tax // 12 cents = $0.12 exactly
Q9: How do you get the maximum value of int32?¶
Answer: Use the math package constants.
import "math"
fmt.Println(math.MaxInt32) // 2147483647
fmt.Println(math.MinInt32) // -2147483648
fmt.Println(math.MaxUint8) // 255
fmt.Println(math.MaxFloat64) // 1.7976931348623157e+308
Q10: What is numeric literal syntax in Go?¶
Answer: Go supports multiple literal formats:
decimal := 1_000_000 // underscore separator (Go 1.13+)
binary := 0b1010_1010 // binary prefix 0b
octal := 0o777 // octal prefix 0o (or old style: 0777)
hex := 0xFF_FF // hexadecimal prefix 0x
float := 1.5e3 // scientific notation: 1500.0
Middle Level Questions¶
Q11: What is the difference between type MyInt int64 and type MyInt = int64?¶
Answer: - type MyInt int64 creates a new defined type. You CANNOT pass MyInt where int64 is expected without explicit conversion. - type MyInt = int64 creates a type alias. They are identical — fully interchangeable.
type UserID int64 // defined type
type ProductID = int64 // alias
func process(id int64) {}
uid := UserID(42)
// process(uid) // COMPILE ERROR: UserID ≠ int64
process(int64(uid)) // OK with conversion
var pid ProductID = 42
process(pid) // OK: ProductID is exactly int64
Q12: How do you safely compare floating-point values?¶
Answer: Use epsilon (tolerance-based) comparison. Never use == for floats in most cases.
import "math"
func floatEqual(a, b, eps float64) bool {
return math.Abs(a-b) < eps
}
// Absolute tolerance: good for small numbers near 0
floatEqual(0.1+0.2, 0.3, 1e-9) // true
// Relative tolerance: better for large numbers
func floatEqualRelative(a, b, tol float64) bool {
diff := math.Abs(a - b)
max := math.Max(math.Abs(a), math.Abs(b))
if max == 0 { return diff == 0 }
return diff/max < tol
}
Q13: When would you use float32 instead of float64?¶
Answer: - Graphics rendering: GPUs are optimized for float32; game engines use float32 for position/color - Large arrays: float32 uses half the memory of float64 — better cache utilization - Interfacing with C/OpenGL: these APIs use float32 - When precision isn't critical: sensor data, graphics coordinates (7 digits is enough)
Avoid float32 for: scientific computing, financial calculations, situations requiring 15+ digits of precision.
Q14: What is uintptr used for?¶
Answer: uintptr is an unsigned integer type large enough to hold any pointer value. It's used for pointer arithmetic with unsafe.Pointer. Unlike pointer types, uintptr is not traced by the GC — do not store uintptr values and expect the pointed-to memory to remain valid.
import "unsafe"
type T struct{ x, y int }
t := T{1, 2}
// Pointer arithmetic (dangerous — for educational purposes)
ptr := unsafe.Pointer(&t)
yPtr := (*int)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.y)))
fmt.Println(*yPtr) // 2
Q15: How do you detect integer overflow in Go?¶
Answer: Use the math/bits package for unsigned overflow, or manual checks for signed.
import "math/bits"
// Unsigned addition overflow
func safeAddUint64(a, b uint64) (uint64, bool) {
result, overflow := bits.Add64(a, b, 0)
return result, overflow != 0
}
// Signed addition overflow (manual check)
func safeAddInt64(a, b int64) (int64, bool) {
result := a + b
// Overflow if signs of a and b are same, but result sign differs
if (a^result)&(b^result) < 0 {
return 0, true
}
return result, false
}
Q16: What is the relationship between int, memory addresses, and slices?¶
Answer: int is the same size as a pointer (on 64-bit: 8 bytes). This is why: - len() and cap() return int — slice lengths are pointer-arithmetic-sized - Slice indices use int — efficient pointer arithmetic - for i := range slice — i is int
Using int64 as slice index requires conversion:
Q17: How does JSON unmarshaling handle large integers?¶
Answer: By default, JSON numbers unmarshal to float64, which can't exactly represent integers > 2^53.
import "encoding/json"
// Problem:
var result map[string]interface{}
json.Unmarshal([]byte(`{"id": 9007199254740993}`), &result)
id := result["id"].(float64) // 9007199254740992 — WRONG!
// Fix: use json.Number
dec := json.NewDecoder(strings.NewReader(`{"id": 9007199254740993}`))
dec.UseNumber()
dec.Decode(&result)
n := result["id"].(json.Number)
id64, _ := n.Int64() // 9007199254740993 — correct
Q18: What is the range of each integer type?¶
Answer:
| Type | Min | Max |
|---|---|---|
int8 | -128 | 127 |
int16 | -32,768 | 32,767 |
int32 | -2,147,483,648 | 2,147,483,647 |
int64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
uint8 | 0 | 255 |
uint16 | 0 | 65,535 |
uint32 | 0 | 4,294,967,295 |
uint64 | 0 | 18,446,744,073,709,551,615 |
Senior Level Questions¶
Q19: Explain why float64 cannot exactly represent all int64 values.¶
Answer: float64 has a 52-bit mantissa (plus an implicit leading 1 bit). This means it can exactly represent integers up to 2^53 = 9,007,199,254,740,992. Beyond this threshold, consecutive float64 values differ by 2 or more — not every integer has a unique representation.
const maxExact = int64(1 << 53) // 9007199254740992
f := float64(maxExact + 1)
fmt.Println(f == float64(maxExact)) // true! precision lost
// Implication: Twitter IDs, large database IDs, etc.
// can be silently corrupted when stored in float64
Q20: How does the Go compiler optimize division by constant integers?¶
Answer: The compiler replaces division by constants with faster sequences: - Division by power of 2: arithmetic right shift (SAR) - Division by other constants: "magic number" multiply + shift (Barrett reduction)
// x / 4 → x >> 2 (for positive; with adjustment for negative)
// x / 10 → x * magic_const >> shift
// This avoids IDIV (35-90 cycles) with 3-6 cycle multiply sequences
Q21: When would struct field ordering affect performance and memory usage?¶
Answer: Poor ordering causes padding (wasted bytes due to alignment requirements). Large-to-small ordering minimizes padding.
import "unsafe"
type Wasteful struct {
a int8 // 1 + 7 padding
b int64 // 8
c int8 // 1 + 7 padding
d int64 // 8
}
fmt.Println(unsafe.Sizeof(Wasteful{})) // 32 bytes
type Efficient struct {
b int64 // 8
d int64 // 8
a int8 // 1
c int8 // 1 + 6 padding
}
fmt.Println(unsafe.Sizeof(Efficient{})) // 24 bytes — 25% smaller
For 10M records: Wasteful = 320MB, Efficient = 240MB.
Q22: What is Kahan summation and when do you need it?¶
Answer: Kahan summation compensates for floating-point rounding errors in cumulative sums. For large lists of float64 values, naive summation accumulates errors; Kahan keeps a "compensation" variable.
func kahanSum(values []float64) float64 {
var sum, comp float64
for _, v := range values {
y := v - comp
t := sum + y
comp = (t - sum) - y // captures lost bits
sum = t
}
return sum
}
// For 10M values of 0.1: naive gives 999999.9999..., Kahan gives 1000000.0
Q23: A database ID field is currently int32. The table has 2 billion records. Should you worry?¶
Answer: Yes — immediately. int32 max is 2,147,483,647. At 2 billion records, you have ~147M IDs left. Depending on insertion rate, you may overflow within months.
Migration strategy:
-- Postgres: ALTER TABLE items ALTER COLUMN id TYPE BIGINT;
-- MySQL: ALTER TABLE items MODIFY id BIGINT AUTO_INCREMENT;
Prevention: Always use int64 for database IDs from the start.
Scenario-Based Questions¶
Scenario Q1: Your service computes financial totals and users report small discrepancies.¶
Code in review:
type Invoice struct {
LineItems []float64
}
func (inv Invoice) Total() float64 {
var total float64
for _, item := range inv.LineItems {
total += item
}
return total
}
What's wrong and how do you fix it?
Answer: Using float64 for financial calculations accumulates floating-point rounding errors. Fix: use int64 cents.
type Invoice struct {
LineItemsCents []int64 // store as cents
}
func (inv Invoice) TotalCents() int64 {
var total int64
for _, item := range inv.LineItemsCents {
total += item
}
return total
}
func (inv Invoice) TotalDollars() string {
cents := inv.TotalCents()
return fmt.Sprintf("$%d.%02d", cents/100, cents%100)
}
Scenario Q2: A rate limiter counter of type int32 occasionally produces incorrect results under high load.¶
What could go wrong?
Answer: Several issues: 1. Overflow: int32 max is ~2.1 billion; high-traffic services can exceed this 2. Data race: if the counter is shared across goroutines without atomic access 3. Wrong type: should be int64 + sync/atomic
// Fix:
var requestCount int64
func increment() {
atomic.AddInt64(&requestCount, 1)
}
func getCount() int64 {
return atomic.LoadInt64(&requestCount)
}
Scenario Q3: Cross-platform code panics on 32-bit ARM but works fine on x86-64.¶
Code:
What's wrong?
Answer: On 32-bit ARM, int is 32 bits (max 2.1B). 4_000_000_000 > math.MaxInt32 — compile error on 32-bit.
// Fix: use int64 explicitly
var maxBuffer int64 = 4_000_000_000
data := make([]byte, maxBuffer) // make accepts int, need conversion
// OR: check platform
if maxBuffer > math.MaxInt {
log.Fatal("buffer too large for this platform")
}
FAQ — Common Interview Topics¶
FAQ1: When should I use int vs int64?¶
- Use
intfor: slice indices, loop counters, anything interacting withlen()/cap() - Use
int64for: database IDs, timestamps, large counters, any cross-platform protocol value
FAQ2: Is there a decimal type in Go?¶
No built-in decimal type. Options: 1. int64 cents for money 2. math/big.Rat for exact rational numbers 3. External library: shopspring/decimal
FAQ3: How do you read/write binary numeric data?¶
import "encoding/binary"
// Write int64 in big-endian
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(12345678))
// Read back
value := int64(binary.BigEndian.Uint64(buf))
FAQ4: What is math.NaN() and why does nan == nan return false?¶
NaN (Not a Number) is a special float64 value representing undefined results (0/0, √-1, etc.). IEEE 754 specifies that NaN is not equal to anything, including itself. Always use math.IsNaN().
FAQ5: How do you convert between int64 and string?¶
import "strconv"
// int64 → string
s := strconv.FormatInt(int64(42), 10) // "42"
s2 := fmt.Sprintf("%d", 42) // "42"
// string → int64
n, err := strconv.ParseInt("42", 10, 64) // 42, nil
if err != nil { /* handle */ }
// int64 → string (simple)
s3 := strconv.Itoa(42) // only for int (not int64!)
FAQ6: What is numeric separator (underscore) in Go?¶
// Go 1.13+: use _ as visual separator in numeric literals
million := 1_000_000
hexColor := 0xFF_FF_FF
binaryFlags := 0b1010_0101
It's purely cosmetic — the compiler ignores underscores.
FAQ7: Operator precedence with numeric types?¶
Common gotcha:
x := 2 + 3*4 // 14 (not 20): * has higher precedence
y := 2 | 3&4 // &^ before |: 2 | (3&4) = 2 | 0 = 2