What are Kotlin's unsigned integer types (UInt/ULong), how do they relate to bitwise work, and what should you watch out for?
answer
- UByte/UShort/UInt/ULong are @JvmInline value classes
- Suffix u/U; toUInt() reinterprets bits
- shr on unsigned is logical — no ushr needed
- No implicit signed<->unsigned mixing
- Java interop sees the signed primitive
basics
~20 sUInt and ULong are integer types that hold only non-negative values by reinterpreting the sign bit as magnitude. They make bit work clearer because right shift never sign-extends, and printing/comparison treat the value as unsigned.
solid answer
~50 sKotlin has UByte, UShort, UInt, and ULong — unsigned counterparts implemented as @JvmInline value classes wrapping the signed primitive, so they're allocation-free at runtime. They expose the same bit functions (and, or, xor, inv, shl, shr) but shr on a UInt is logical (zero-fill) by definition — there's no separate ushr because unsigned has no sign bit. You create them with literal suffix u/U (255u, 0xFFFFFFFFu) or conversions (val x = (-1).toUInt() // 4294967295u). toString, comparison, and division all interpret the bits as unsigned. Caveats: they are still experimental-adjacent / opt-in in some contexts, arithmetic wraps (no overflow exception), mixing signed and unsigned requires explicit conversion, and Java interop sees them as the underlying signed primitive. They shine for bit fields, hashing, byte parsing, and color/network protocols where you want unsigned semantics without manual masking.
code
kotlin · 6 linesval raw = (-1).toUInt() // 4294967295u (same bits as -1)
println(raw) // 4294967295
println(raw shr 1) // 2147483647u (logical shift)
val color = 0xAABBCCDDu
val red = (color shr 16 and 0xFFu).toUByte() // 0xCC... extract channel
val back: Int = raw.toInt() // -1, signed reinterpretationgo deeper
Knows UInt/ULong exist and hold only non-negative values via the u suffix.
Explains literal suffixes, that shr is logical, and bit reinterpretation via toUInt/toInt.
Knows they're inline value classes, the no-implicit-mixing rule, and uses them to clean up byte/channel extraction.
Weighs Java interop cost, opt-in array semantics, and when unsigned modeling is worth it vs masking on signed types.
## What they are Kotlin provides four **unsigned integer types**: `UByte`, `UShort`, `UInt`, `ULong`. Each is a `@JvmInline value class` wrapping the corresponding signed primitive — meaning at runtime a `UInt` is just an `Int` with no boxing, but the compiler reinterprets its bit pattern as a **non-negative magnitude** (0 .. 4294967295 for UInt). ## Creating them ```kotlin val a = 255u // UInt via literal suffix u/U val b = 0xFFFF_FFFFu // max UInt val c = 10uL // ULong val d = (-1).toUInt() // 4294967295u — reinterpret the bits val e = b.toInt() // -1 — same bits, signed view ``` ## Relation to bitwise operations Unsigned types implement the same infix bit functions — `and`, `or`, `xor`, `inv`, `shl`, `shr`. The key payoff: there is **no `ushr` on unsigned types** because `shr` is *already* logical (zero-fill) — an unsigned value has no sign bit to extend. So `0xFFFFFFFFu shr 1 == 0x7FFFFFFFu`, no surprises. ```kotlin val rgba = 0xAABBCCDDu val a = (rgba shr 24).toUByte() // 0xAA, clean, no masking against sign val b = (rgba shr 16 and 0xFFu).toUByte() ``` This removes the classic `and 0xFF` / `ushr` dance you need with signed `Int`. ## Semantic differences from signed - **Printing:** `(-1).toUInt()` prints `4294967295`, not `-1`. - **Comparison & division:** `5u / 2u` and `a < b` use unsigned ordering. - **Wraparound:** arithmetic wraps silently; there is no overflow check (same as signed). ## Caveats / gotchas 1. **Opt-in surface:** the types are stable, but some APIs/literals historically required `@OptIn(ExperimentalUnsignedTypes::class)` for unsigned **arrays** (`UIntArray`). Scalars are fine. 2. **No implicit mixing:** you cannot add a `UInt` to an `Int` without an explicit `toUInt()`/`toInt()`; the compiler rejects it to avoid sign confusion. 3. **Java interop:** Java sees the underlying signed primitive (mangled method names), so unsigned types are awkward to expose across the JVM boundary. 4. **Conversions reinterpret bits** (`toUInt`) vs **clamp/widen** depending on the function — know which you call. ## When to use Bit fields, hashing, parsing binary/network/color formats, and anywhere a value is conceptually a magnitude or raw bit container. For ordinary domain numbers, prefer plain `Int`/`Long`. ## Summary UInt/ULong are inline value classes giving true unsigned semantics; `shr` is logical so no `ushr` exists, printing/comparison/division are unsigned, and the main pitfalls are no implicit signed-unsigned mixing and clunky Java interop.
- Why is there no ushr on UInt?An unsigned value has no sign bit to propagate, so shr already zero-fills. ushr would be identical, so the stdlib doesn't define it.
- What happens at runtime when you use a UInt — is it boxed?No. Unsigned types are @JvmInline value classes, so a UInt compiles down to a plain int with no allocation in the common case; boxing only occurs when used as a generic/nullable type.
Unsigned types are the same ruler turned around so the negative half is relabeled as the high positive numbers — same marks, different reading.
saying these in an interview costs you the question
- Thinking UInt is a separate boxed object on the heap
- Expecting (-1).toUInt() to print -1
- Adding a UInt and Int without conversion and expecting it to compile
- Looking for ushr on unsigned types
- Assuming smooth Java interop for unsigned types