📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

πŸ“š Interview question

Explain binary, decimal and hexadecimal. How do you convert between them, and why do developers use hexadecimal so often?

Positional number systems (base 2, 10 and 16), converting between them by hand and in C#, and why hex is the natural way to read bytes, memory addresses, colours, hashes and GUIDs.

Beginner Theory Beginner binary bits-and-bytes computer-basics hexadecimal number-systems

🎯 Key points

  • All three are positional systems; only the base (2, 10, 16) differs, and the same integer can be written exactly in any of them
  • Computers store everything in binary because hardware has two stable states; the type's bit width (8, 16, 32, 64) sets the range, whatever base you write the value in
  • One hex digit is exactly four bits (a nibble), so two hex digits are exactly one byte and byte boundaries stay visible
  • Decimal to binary: divide by 2 repeatedly and read the remainders from bottom to top; to decimal: sum digit × base^position; binary to hex: group bits into nibbles from the right and convert each nibble to one hex digit
  • C# has 0b/0x literals with digit separators; ToString("X2")/("x2") formats upper/lower-case hex, ToString("B8") formats binary (added in .NET 8), and Convert.ToHexString/FromHexString handle byte arrays
  • A hex literal is a number but a hex string ("FF") is text: parse before comparing; 0x80000000 is a uint literal, and narrowing it to int needs an explicit unchecked cast
  • Hex is the working notation for bitwise work and debugging (masks, flags, addresses, hex dumps, hashes, HRESULTs); keep data as bytes internally and convert to hex only at the edges

⏱ 30-second answer

They're all positional number systems; only the base changes. Decimal is base 10 (digits 0–9), binary is base 2 (0 and 1) and hexadecimal is base 16 (0–9 then A–F). Computers store everything in binary because transistors have two states. Developers use hex because one hex digit is exactly four bits, so a byte is always two hex digits: 0xFF is 1111 1111 is 255. That makes memory addresses, hashes, GUIDs and byte dumps short and easy to read. In C# you write 0b1010 or 0xFF and format with ToString("X2") or Convert.ToHexString.

πŸ—£ Full answer (senior)

Positional systems

A number's value is the sum of each digit multiplied by the base raised to its position (counting from 0 on the right).

System Base Digits Example Value
Decimal 10 0–9 255 2Γ—100 + 5Γ—10 + 5Γ—1
Binary 2 0–1 1111 1111 128+64+32+16+8+4+2+1 = 255
Hexadecimal 16 0–9, A–F FF 15Γ—16 + 15 = 255

The notation never changes what can be stored. A 32-bit int overflows at 2,147,483,647 whether you write it as 2147483647, 0x7FFFFFFF or 0b0111...1.

Why binary

Digital hardware stores and switches between two reliable states (high/low voltage, charged/not charged). Every value in memory, whether a number, character, pixel or instruction, is a pattern of bits. Eight bits make a byte, the smallest addressable unit of memory.

Why hexadecimal

  • 16 = 2⁴, so each hex digit maps to exactly 4 bits (a nibble). Converting is a lookup, not arithmetic.
  • A byte is always two hex digits (00 to FF), so byte arrays, hashes and memory dumps line up neatly.
  • Binary is too long to read (11011110101011011011111011101111); decimal hides the byte boundaries. Hex shows both: DEADBEEF.
Where you see hex as a .NET developer Example
Memory addresses in the debugger and dumps 0x00007FF8A1B2C3D0
Hashes, GUIDs, ETags SHA-256 = 64 hex characters
HRESULT error codes 0x80070005 (access denied)
Colours and Unicode #1E90FF, U+00E9
File signatures 25 50 44 46 = %PDF

Converting

  • Binary/hex β†’ decimal: multiply and add (as in the table).
  • Decimal β†’ binary/hex: divide by the base repeatedly and read the remainders backwards.
  • Binary ↔ hex: group bits in fours from the right and map each group.

In C# and .NET

Task API
Literals 0b1111_1111, 0xFF, 1_000_000
Format a number 255.ToString("X2") β†’ FF, "x2" β†’ ff, "B8" β†’ 11111111
Bytes to hex Convert.ToHexString(bytes), Convert.ToHexStringLower(bytes)
Hex to bytes Convert.FromHexString(text) (throws on odd length or bad characters)
Parse a number int.TryParse("FF", NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var v), Convert.ToInt32("1010", 2)

Hex is a text representation: it doubles the size of the data and each conversion allocates a string. Keep data as byte[] or Span<byte> internally and convert only for logs, URLs and display.

Production example

A payment integration returned a signature mismatch. Logging the computed HMAC with Convert.ToHexString and comparing it with the provider's lowercase hex showed the bytes matched but the case did not. Comparing the decoded bytes with CryptographicOperations.FixedTimeEquals fixed it and also made the comparison constant-time. Being fluent in hex made it a five-minute fix instead of an afternoon of guessing.

πŸ” What the interviewer is checking

A warm-up that checks fundamentals and comfort with low-level representations.

Green flags: explains positional notation, converts quickly, knows the four-bits-per-hex-digit rule, gives real places hex appears, knows the C# literals and formatting.

Red flags: can't convert a small number, can't explain why hex is used, confuses bits and bytes.

πŸ“š Learn in depth

1. Place values side by side

position:     7    6    5    4    3    2    1    0
binary:     128   64   32   16    8    4    2    1      (2^n)
hex:          -    -    -    - 4096  256   16    1      (16^n)
decimal:  10^7 ...                1000  100   10    1      (10^n)

2. Worked conversions

Decimal 202 to binary (divide by 2, read remainders bottom-up):

202 / 2 = 101 r0
101 / 2 =  50 r1
 50 / 2 =  25 r0
 25 / 2 =  12 r1
 12 / 2 =   6 r0
  6 / 2 =   3 r0
  3 / 2 =   1 r1
  1 / 2 =   0 r1      ->  1100 1010

Binary to hex (group in fours): 1100 1010 β†’ C A β†’ 0xCA. Hex to decimal: 0xCA = 12Γ—16 + 10 = 202.

3. The nibble table to memorise

Hex Binary Dec Hex Binary Dec
0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 A 1010 10
3 0011 3 B 1011 11
4 0100 4 C 1100 12
5 0101 5 D 1101 13
6 0110 6 E 1110 14
7 0111 7 F 1111 15

4. Useful powers of two

Power Value Where you meet it
2⁸ 256 Values in a byte
2¹⁰ 1,024 KiB
2¹⁢ 65,536 Ports, ushort, UTF-16 code units
2Β³ΒΉ βˆ’ 1 2,147,483,647 int.MaxValue
2Β³Β² ~4.29 billion IPv4 addresses, uint
2⁢⁴ ~1.8 Γ— 10¹⁹ ulong

Note the difference between KB (1,000 bytes) and KiB (1,024 bytes). Disk vendors use KB/MB/GB; Windows Explorer and many tools show KiB/MiB/GiB values with KB/MB/GB labels.

5. Hex as a debugging tool

Hex keeps byte boundaries visible, so patterns jump out that decimal hides.

What you're looking at What to recognise in hex
A hex dump of an unknown file File signatures ("magic numbers"): 25 50 44 46 = %PDF, 50 4B 03 04 = PK (ZIP, .docx, .xlsx, .nupkg), 89 50 4E 47 = PNG, 1F 8B = gzip
A text file with odd characters EF BB BF = UTF-8 BOM, C3 A9 = UTF-8 Γ©, 0D 0A = CRLF line ending
An exception message HRESULTs such as 0x80070005 (access denied) and 0x80131500 (generic .NET COR_E_EXCEPTION)
The Memory window or a dump Addresses like 0x00007FF8... (64-bit), object headers and method table pointers
A corrupted buffer or protocol frame ASCII text runs (41–5A letters) where binary should be, repeated fill bytes, or a length field that reads as a huge number

Example: an upload API rejected some "PDF" files. A hex dump of the first bytes showed 50 4B 03 04: they were Word documents renamed to .pdf. Checking the signature instead of the file extension fixed the validation, and also closed a hole where any file type could be uploaded with a .pdf name.

6. Hex in real artefacts

  • GUID: 3F2504E0-4F89-11D3-9A0C-0305E82C3301 is 16 bytes in hex (with mixed byte order in the first three groups; see the endianness question).
  • HRESULT: 0x80070002 means a Win32 error (facility 7) with code 2, "file not found".
  • Hex dump: offset, 16 bytes in hex, then the printable ASCII characters. This is how you inspect files and network packets.

7. Octal, briefly

Base 8 (three bits per digit) still appears in Unix file permissions: chmod 755 means rwx r-x r-x. C# has no octal literal; use Convert.ToInt32("755", 8) or UnixFileMode flags.

8. Why built-in conversions beat hand-written ones

  • Convert.FromHexString rejects odd-length strings and invalid characters with a FormatException; hand-written parsers often accept garbage silently, which matters when parsing untrusted input such as tokens or signatures.
  • Convert.ToHexString is vectorised and allocates once; string concatenation in a loop is slow and allocation-heavy.
  • For single values where bad input is expected, use int.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value) instead of catching exceptions.

9. Try it yourself

  1. Convert 173 to binary and hex by hand, then check with 173.ToString("B8") and 173.ToString("X2").
  2. Compute the SHA-256 of "hello" with SHA256.HashData and print it with Convert.ToHexStringLower. Count the characters (64) and explain why.
  3. Write a tiny hex-dump tool that prints a file 16 bytes per line with offsets, then run it on a UTF-8 text file containing Γ© and spot the two-byte sequence C3 A9.
  4. Run the same tool on a .docx and a .pdf and identify each from its first four bytes.

πŸ’» Code

namespace NumberSystems;

using System.Globalization;
using System.Security.Cryptography;
using System.Text;

public static class Program
{
    public static void Main()
    {
        int value = 0b1100_1010;                    // binary literal with a digit separator
        Console.WriteLine(value);                   // 202
        Console.WriteLine(value.ToString("X2"));    // CA
        Console.WriteLine(value.ToString("x2"));    // ca
        Console.WriteLine(value.ToString("B8"));    // 11001010

        bool ok = int.TryParse("CA", NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int fromHex);
        int fromBinary = Convert.ToInt32("11001010", 2);
        Console.WriteLine(ok && fromHex == fromBinary);  // True

        // A hex literal above int.MaxValue is a uint
        uint mask = 0x80000000;
        int asInt = unchecked((int)mask);           // -2147483648: same bits, different meaning
        Console.WriteLine($"{mask} {asInt}");

        byte[] hash = SHA256.HashData(Encoding.UTF8.GetBytes("hello"));
        Console.WriteLine(Convert.ToHexStringLower(hash)); // 64 hex chars

        // Compare secrets as bytes, in constant time, not as hex strings
        byte[] received = Convert.FromHexString(Convert.ToHexString(hash));
        Console.WriteLine(CryptographicOperations.FixedTimeEquals(hash, received)); // True

        HexDump(Encoding.UTF8.GetBytes("CafΓ© β˜•"));
    }

    private static void HexDump(ReadOnlySpan<byte> data)
    {
        for (var offset = 0; offset < data.Length; offset += 16)
        {
            var line = data.Slice(offset, Math.Min(16, data.Length - offset));
            var hex = new StringBuilder();
            var text = new StringBuilder();
            foreach (var b in line)
            {
                hex.Append(b.ToString("X2")).Append(' ');
                text.Append(b is >= 0x20 and < 0x7F ? (char)b : '.');
            }
            Console.WriteLine($"{offset:X8}  {hex,-48} {text}");
        }
    }
}

⚠ Common mistakes

  • Mistake: Reading hex digits as decimal. Why it is wrong: 0x10 is 16, not 10, and 0x80070005 is a bit pattern, not a large decimal number. Instead: Always note the base, and keep the 0x prefix when writing or logging hex values.
  • Mistake: Comparing a hex string with a number. Why it is wrong: "FF" is two characters of text; 0xFF is the integer 255, so comparing formatted strings ("255" vs "FF") fails. Instead: Parse first and compare numbers or bytes.
  • Mistake: Parsing hex from APIs and config with int.Parse or a hand-written loop. Why it is wrong: Parse throws on bad input (exceptions are slow and noisy on hot paths), and hand-written loops accept odd-length strings or stray characters, which matters for tokens and signatures from third parties. Instead: Use int.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var v) for numbers and Convert.FromHexString (or the span-based Convert.FromHexString(ReadOnlySpan<char>, Span<byte>, out _, out _) overload in .NET 9+) for bytes; both validate input and are vectorised.
  • Mistake: Assuming a hex literal fits in int. Why it is wrong: 0x80000000 and 0xFFFFFFFF are uint literals; int x = 0xFFFFFFFF; does not compile, and unchecked((int)0xFFFFFFFF) is -1. Instead: Use the type that matches the width, and unchecked casts only when you mean a bit pattern.
  • Mistake: Formatting bytes with ToString("X"). Why it is wrong: It drops leading zeros, so 0x0A prints as A and joined strings become ambiguous. Instead: Use "X2" per byte or Convert.ToHexString for whole arrays.
  • Mistake: Comparing hex strings with case-sensitive equality. Why it is wrong: "ff" and "FF" are the same byte, so a signature check fails for no real reason. Instead: Compare decoded bytes, with CryptographicOperations.FixedTimeEquals for secrets.
  • Mistake: Storing hashes and IDs as hex text in the database. Why it is wrong: A SHA-256 as char(64) takes twice the space of binary(32), makes indexes bigger and slower, and invites case mismatches. Instead: Store binary/varbinary (or uniqueidentifier for GUIDs) and convert to hex only for display.
  • Mistake: Mixing up KB and KiB in capacity maths. Why it is wrong: 1 GB is 1,000,000,000 bytes but 1 GiB is 1,073,741,824 bytes, so a "500 GB" disk shows about 465 GiB and limits drift by about 7% at the gigabyte scale. Instead: Say which unit you mean and use binary units for memory.

πŸ› Senior insight: trade-offs

Fluency in hex is a debugging and performance skill, not trivia. Seniors read hex dumps to diagnose encoding bugs and corrupted serialisation, recognise HRESULTs and Win32 error codes in exceptions, verify hashes and signatures, and read object headers and field layouts in memory dumps when chasing memory or cache problems.

They separate notation from storage: binary, decimal and hex are only ways to write a value; the type's width decides the range, and integer conversions between the bases are always exact. They use hex for bitwise work, because masks like 0xFF00 or 0x0F show exactly which bits are kept.

They treat hex strings as a presentation format: keep bytes as bytes internally (Span<byte>, BinaryPrimitives), convert with Convert.ToHexString only at boundaries, and compare secrets as bytes in constant time. Logging a token in hex is still logging the token.

πŸ”— Follow-ups the interviewer may ask

  1. When: Candidate explains that a byte holds 0 to 255. Why: Moves from notation to how numbers are actually stored, including negatives and overflow.
    How to answer

    A bit is a single 0 or 1; a byte is 8 bits and is the smallest unit memory addresses. C# numeric types have fixed sizes on every platform: byte 1 byte, short 2, int 4, long 8, Int128 16, Half 2, float 4, double 8, decimal 16. Signed integers use two's complement, so int runs from βˆ’2,147,483,648 to 2,147,483,647. Integer arithmetic is unchecked by default: int.MaxValue + 1 silently wraps to int.MinValue, while float/double never throw and produce Infinity or NaN, and decimal always throws. Make overflow explicit where a wrap would be a bug (money, counters, sizes from external input): use checked, a wider type, or generic-math conversions such as int.CreateChecked(x) and int.CreateSaturating(x).

  2. When: Candidate mentions Unicode code points written in hex, such as U+00E9. Why: Checks that the candidate can apply byte-level thinking to text.
    How to answer

    Computers store numbers, so text needs two agreements: a character set that gives each character a number, and an encoding that turns those numbers into bytes and back. ASCII is both at once: 128 characters (0–127) stored in 7 bits, enough for English but not Γ©, ā or 汉. Unicode is a character set only: it gives every character in every script a code point from U+0000 to U+10FFFF (U+0041 is A, U+00E9 is Γ©). UTF-8, UTF-16 and UTF-32 are the encodings that store code points as code units of 8, 16 or 32 bits. What a user sees as one character, a grapheme cluster, can be several code points (an emoji with a skin-tone modifier, or e plus a combining accent), and the same text can be written in composed or decomposed form, so applications normalise (usually to NFC) before comparing or storing user input.

↩ Usually asked after

More in Computer Basics

Last updated 23 Oct 2026.

🌐 Sangam's Webring

βͺ Previous 🏠 Main Next ⏩
πŸ’¬ Chat with me