Skip to main content

🔄 Lesson 2.3: Type Conversion and Coercion

When types collide — how to convert data intentionally, and why JavaScript sometimes does it behind your back.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Distinguish between explicit conversion (you ask for it) and implicit coercion (the language does it automatically)
  • Convert between strings, numbers, and booleans in all three languages
  • Explain JavaScript's coercion rules and why "5" + 3"5" - 3
  • Handle user input that arrives as a string but needs to be a number
  • Avoid common type conversion bugs

Estimated Time: 45 minutes

Project: A "Type Converter Playground" that converts user input between types

📑 In This Lesson

Explicit vs. Implicit Conversion

There are two ways a value's type can change:

graph LR A["Type Conversion"] --> B["Explicit
(You ask for it)"] A --> C["Implicit / Coercion
(Language does it)"] B --> D["int('42')
Number('42')
int.Parse('42')"] C --> E["'5' + 3 → '53'
(JS converts number
to string silently)"] style A fill:#3b82f6,stroke:#1e40af,color:#fff,stroke-width:2px style B fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px style C fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:2px
  • Explicit conversion — you write code that says "convert this to a number." It's clear, intentional, and predictable.
  • Implicit coercion — the language automatically converts a value behind the scenes to make an operation work. Sometimes helpful, sometimes shocking.

📖 Language Philosophies

Python says: "Explicit is better than implicit." It almost never coerces types — you must convert manually.
JavaScript says: "I'll try to make it work." It coerces aggressively, which causes famous gotchas.
C# says: "I'll help with safe conversions." It allows some implicit conversions (small → big) but blocks lossy ones.

String → Number

This is the most common conversion you'll need — especially when dealing with user input, which almost always arrives as a string.

# Python: use int() and float()
age_str = "25"
price_str = "19.99"

age = int(age_str)         # 25 (integer)
price = float(price_str)   # 19.99 (float)

print(age + 5)     # 30
print(price * 2)   # 39.98

# What if the string isn't a valid number?
# bad = int("hello")  # ValueError: invalid literal
# bad = int("12.5")   # ValueError: can't convert float string to int
# Use float() first: int(float("12.5"))  → 12
// JavaScript: several options
let ageStr = "25";
let priceStr = "19.99";

// Number() — the cleanest option
let age = Number(ageStr);        // 25
let price = Number(priceStr);    // 19.99

// parseInt() and parseFloat() — more forgiving
let parsed1 = parseInt("42px");      // 42 (stops at non-digit)
let parsed2 = parseFloat("3.14abc"); // 3.14

// The + shortcut (unary plus)
let quick = +"25";  // 25

// What if the string isn't a valid number?
console.log(Number("hello"));   // NaN (Not a Number)
console.log(Number(""));        // 0 (empty string → 0!)
console.log(Number("  "));      // 0 (whitespace → 0!)
// C#: Parse() or Convert methods
string ageStr = "25";
string priceStr = "19.99";

int age = int.Parse(ageStr);          // 25
double price = double.Parse(priceStr); // 19.99

// Convert class (alternative)
int age2 = Convert.ToInt32(ageStr);    // 25
double price2 = Convert.ToDouble(priceStr); // 19.99

// What if the string isn't valid?
// int bad = int.Parse("hello");  // FormatException!

// TryParse — safe conversion (no crash)
if (int.TryParse("42", out int result))
{
    Console.WriteLine($"Parsed: {result}");  // Parsed: 42
}
else
{
    Console.WriteLine("Not a valid number");
}

String → Number Cheat Sheet

Task 🐍 Python ⚡ JavaScript 🔷 C#
String → Integer int("42") Number("42") or parseInt("42") int.Parse("42")
String → Decimal float("3.14") Number("3.14") or parseFloat("3.14") double.Parse("3.14")
Invalid string ValueError (crash) NaN (silent failure) FormatException (crash)
Safe conversion try/except isNaN() check int.TryParse()

⚠️ JavaScript's Silent Failures

JavaScript's Number("hello") returns NaN instead of crashing. This sounds convenient, but NaN is contagious — any math with NaN produces NaN. Your program keeps running with garbage data instead of stopping at the real problem. Always check with isNaN() after converting.

Number → String

Converting numbers to strings is simpler — every number has a valid string representation.

# Python: use str()
age = 25
price = 19.99

age_str = str(age)       # "25"
price_str = str(price)   # "19.99"

# Useful for concatenation
print("Age: " + str(age))   # "Age: 25"

# But f-strings handle this automatically!
print(f"Age: {age}")         # "Age: 25" (easier!)
// JavaScript: several options
let age = 25;
let price = 19.99;

// String() function
let ageStr = String(age);        // "25"

// .toString() method
let priceStr = price.toString(); // "19.99"

// Concatenation trick (implicit coercion)
let quick = age + "";            // "25"
let quick2 = "" + price;         // "19.99"

// Template literals handle it automatically
console.log(`Age: ${age}`);      // "Age: 25"
// C#: .ToString() or Convert
int age = 25;
double price = 19.99;

string ageStr = age.ToString();       // "25"
string priceStr = price.ToString();   // "19.99"

// Convert class
string ageStr2 = Convert.ToString(age);  // "25"

// Formatting while converting
string formatted = price.ToString("F2");   // "19.99"
string currency = price.ToString("C");     // "$19.99"

// Interpolation handles it automatically
Console.WriteLine($"Age: {age}");  // "Age: 25"

✅ Pro Tip: Just Use String Interpolation

In practice, you rarely need explicit number-to-string conversion because string interpolation handles it automatically. When you write f"Score: {score}" (Python), `Score: ${score}` (JS), or $"Score: {score}" (C#), the number is converted for you.

Boolean Conversions

Any value can be interpreted as a boolean. The rules for what's "truthy" (acts like true) and "falsy" (acts like false) are important for writing conditions.

# Python: use bool() to see the boolean value

# Falsy values (these all become False):
print(bool(0))        # False
print(bool(0.0))      # False
print(bool(""))       # False (empty string)
print(bool([]))       # False (empty list)
print(bool(None))     # False

# Truthy values (everything else is True):
print(bool(1))        # True
print(bool(-5))       # True (any non-zero number)
print(bool("hello"))  # True (any non-empty string)
print(bool(" "))      # True (space is not empty!)
print(bool([1,2]))    # True (non-empty list)
// JavaScript: use Boolean() to see the boolean value

// Falsy values (these all become false):
console.log(Boolean(0));          // false
console.log(Boolean(""));         // false (empty string)
console.log(Boolean(null));       // false
console.log(Boolean(undefined));  // false
console.log(Boolean(NaN));        // false

// Truthy values (everything else is true):
console.log(Boolean(1));          // true
console.log(Boolean(-5));         // true
console.log(Boolean("hello"));    // true
console.log(Boolean(" "));        // true
console.log(Boolean("0"));        // true! ("0" is non-empty)
console.log(Boolean([]));         // true! (empty array is truthy!)
// C#: strict — no automatic truthy/falsy!

// C# does NOT let you use numbers or strings as booleans:
// if (1) {}        // ERROR: Cannot convert int to bool
// if ("hello") {}  // ERROR: Cannot convert string to bool

// You must use explicit boolean expressions:
int count = 5;
if (count > 0)  // explicit comparison — this is OK
{
    Console.WriteLine("Has items");
}

// Convert explicitly with Convert.ToBoolean
Console.WriteLine(Convert.ToBoolean(0));     // False
Console.WriteLine(Convert.ToBoolean(1));     // True
Console.WriteLine(Convert.ToBoolean("true")); // True
// Convert.ToBoolean("hello")  // FormatException!

⚠️ JavaScript Gotcha: "0" is Truthy!

In JavaScript, the string "0" is truthy (because it's a non-empty string), but the number 0 is falsy. And an empty array [] is truthy, even though it contains nothing! These quirks trip up even experienced developers.

🎓 Instructor Note: Delivery Guidance

The truthy/falsy concept is best taught through live experimentation. Open a Python REPL and JavaScript console side by side, and have students predict the output of bool([]) vs Boolean([]). The different result (Python: False, JS: true) is a great hook. Emphasize that C# avoids this entire category of bugs by refusing to treat non-booleans as booleans.

JavaScript's Coercion Surprises

JavaScript is uniquely aggressive about implicit type conversion. Let's see the famous examples:

# Python: refuses to mix types — CLEAR errors

# print("5" + 3)     # TypeError!
# print("5" - 3)     # TypeError!
# print("5" * 3)     # "555" — this one works! (string repetition)
# print("5" * "3")   # TypeError!

# Python's philosophy: if it's ambiguous, crash.
# This means bugs are found EARLY.
// JavaScript: wild implicit coercion

// The + operator: if EITHER side is a string, concatenate
console.log("5" + 3);      // "53" (number → string, then join)
console.log(5 + "3");      // "53"
console.log("5" + true);   // "5true"

// Other operators: convert strings TO numbers
console.log("5" - 3);      // 2  (string → number, then subtract)
console.log("5" * 3);      // 15 (string → number, then multiply)
console.log("5" / "2");    // 2.5

// The REALLY weird stuff:
console.log(true + true);      // 2
console.log(true + "1");       // "true1"
console.log([] + []);          // "" (empty string!)
console.log([] + {});          // "[object Object]"
console.log({} + []);          // 0 (or "[object Object]" — depends!)

// Why does + behave differently from - ?
// + is BOTH addition AND concatenation.
// - is ONLY subtraction, so JS converts to numbers.
// C#: allows SAFE implicit conversions only

// Small type → big type: OK (no data loss)
int small = 42;
double big = small;     // int → double (implicit, safe)
long bigger = small;    // int → long (implicit, safe)

// Big type → small type: REQUIRES explicit cast
double pi = 3.14;
// int truncated = pi;           // ERROR: cannot implicitly convert
int truncated = (int)pi;         // 3 — explicit cast required

// String + number: converts number to string (like JS)
Console.WriteLine("Score: " + 42);   // "Score: 42"

// But string - number: NOT allowed (unlike JS)
// Console.WriteLine("5" - 3);      // ERROR!
💡 The + Operator Rule: In JavaScript, + does double duty as both addition and string concatenation. If either operand is a string, + chooses concatenation. All other math operators (-, *, /) can only do math, so they convert strings to numbers.

✅ How to Stay Safe

  • Python: You're already safe — Python forces you to be explicit.
  • JavaScript: Always convert before math: Number(x) + Number(y). Use === instead of ==. Lint tools like ESLint catch common coercion traps.
  • C#: The compiler has your back — it blocks unsafe conversions at compile time.

Real-World Scenario: User Input

In real programs, user input almost always arrives as a string — even if the user typed a number. Here's how to handle it:

# Python: input() always returns a string
user_input = input("Enter your age: ")   # e.g., user types "25"
print(type(user_input))   # <class 'str'>

# Must convert before doing math!
age = int(user_input)
birth_year = 2026 - age
print(f"You were born around {birth_year}")

# Safe version with error handling
try:
    age = int(input("Enter your age: "))
    print(f"Next year you'll be {age + 1}")
except ValueError:
    print("That's not a valid number!")
// Browser: prompt() always returns a string (or null)
let userInput = prompt("Enter your age:");  // e.g., "25"
console.log(typeof userInput);  // "string"

// Must convert before doing math!
let age = Number(userInput);

// Check for invalid input
if (isNaN(age) || userInput === null) {
    console.log("That's not a valid number!");
} else {
    let birthYear = 2026 - age;
    console.log(`You were born around ${birthYear}`);
}

// Node.js uses readline (more complex — shown later)
// C#: Console.ReadLine() always returns a string
Console.Write("Enter your age: ");
string userInput = Console.ReadLine();  // e.g., "25"

// Safe conversion with TryParse
if (int.TryParse(userInput, out int age))
{
    int birthYear = 2026 - age;
    Console.WriteLine($"You were born around {birthYear}");
}
else
{
    Console.WriteLine("That's not a valid number!");
}
🎓 Instructor Note: Delivery Guidance

This is a great section to do as a live coding exercise. Have students build the "age calculator" themselves. The most common mistake: forgetting to convert input() to int() and getting string concatenation instead of addition. Let them hit that bug, then fix it. C#'s TryParse pattern is elegant and worth highlighting as a best practice — it's the safest approach of the three.

Exercises

🏋️ Exercise 1: Temperature Converter

Objective: Write a program that converts a temperature from Fahrenheit to Celsius.

  1. Store a Fahrenheit temperature as a string (simulating user input): "98.6"
  2. Convert it to a number
  3. Apply the formula: celsius = (fahrenheit - 32) × 5 / 9
  4. Display the result with one decimal place using string interpolation
✅ Solution
fahr_str = "98.6"
fahr = float(fahr_str)
celsius = (fahr - 32) * 5 / 9
print(f"{fahr}°F = {celsius:.1f}°C")
# 98.6°F = 37.0°C
let fahrStr = "98.6";
let fahr = Number(fahrStr);
let celsius = (fahr - 32) * 5 / 9;
console.log(`${fahr}°F = ${celsius.toFixed(1)}°C`);
// 98.6°F = 37.0°C
string fahrStr = "98.6";
double fahr = double.Parse(fahrStr);
double celsius = (fahr - 32) * 5.0 / 9;
Console.WriteLine($"{fahr}°F = {celsius:F1}°C");
// 98.6°F = 37.0°C

🏋️ Exercise 2: Coercion Predictor

Objective: Without running the code, predict what each expression produces in JavaScript. Then check your answers.

  1. "10" + 5
  2. "10" - 5
  3. "10" * "2"
  4. true + 1
  5. "hello" - 1
  6. "" + 0
  7. Boolean("false")
✅ Answers
console.log("10" + 5);        // "105" — string wins with +
console.log("10" - 5);        // 5    — only math, converts to number
console.log("10" * "2");      // 20   — both convert to numbers
console.log(true + 1);        // 2    — true becomes 1
console.log("hello" - 1);     // NaN  — "hello" can't become a number
console.log("" + 0);          // "0"  — string wins with +
console.log(Boolean("false")); // true — "false" is a non-empty string!
# In Python, most of these would be TypeErrors:
# "10" + 5      → TypeError
# "10" - 5      → TypeError
# "10" * "2"    → TypeError
# True + 1      → 2 (booleans ARE ints in Python)
# "hello" - 1   → TypeError
# "" + 0         → TypeError
# bool("false")  → True (non-empty string)
// In C#, most won't even compile:
// "10" + 5      → "105" (string concatenation)
// "10" - 5      → Compile Error!
// "10" * "2"    → Compile Error!
// true + 1      → Compile Error!
// "hello" - 1   → Compile Error!
// "" + 0         → "0" (string concatenation)
// Convert.ToBoolean("false") → false (C# parses the word)

🏋️ Exercise 3: Receipt Calculator

Objective: Build a receipt calculator that handles string inputs.

  1. Start with these string values (simulating form input): item name = "Widget", price = "12.50", quantity = "3", tax rate = "0.08"
  2. Convert to the appropriate types
  3. Calculate subtotal, tax amount, and total
  4. Display a formatted receipt with currency formatting
✅ Solution
item_name = "Widget"
price_str = "12.50"
qty_str = "3"
tax_rate_str = "0.08"

price = float(price_str)
qty = int(qty_str)
tax_rate = float(tax_rate_str)

subtotal = price * qty
tax = subtotal * tax_rate
total = subtotal + tax

print(f"Item:     {item_name} x{qty}")
print(f"Subtotal: ${subtotal:.2f}")
print(f"Tax:      ${tax:.2f}")
print(f"Total:    ${total:.2f}")
let itemName = "Widget";
let priceStr = "12.50";
let qtyStr = "3";
let taxRateStr = "0.08";

let price = Number(priceStr);
let qty = Number(qtyStr);
let taxRate = Number(taxRateStr);

let subtotal = price * qty;
let tax = subtotal * taxRate;
let total = subtotal + tax;

console.log(`Item:     ${itemName} x${qty}`);
console.log(`Subtotal: $${subtotal.toFixed(2)}`);
console.log(`Tax:      $${tax.toFixed(2)}`);
console.log(`Total:    $${total.toFixed(2)}`);
string itemName = "Widget";
string priceStr = "12.50";
string qtyStr = "3";
string taxRateStr = "0.08";

decimal price = decimal.Parse(priceStr);
int qty = int.Parse(qtyStr);
decimal taxRate = decimal.Parse(taxRateStr);

decimal subtotal = price * qty;
decimal tax = subtotal * taxRate;
decimal total = subtotal + tax;

Console.WriteLine($"Item:     {itemName} x{qty}");
Console.WriteLine($"Subtotal: {subtotal:C}");
Console.WriteLine($"Tax:      {tax:C}");
Console.WriteLine($"Total:    {total:C}");
🎓 Instructor Note: Delivery Guidance

Exercise 2 (Coercion Predictor) works brilliantly as a class activity. Put the expressions on screen and have students vote on what they think the output is. The reveal is always entertaining, especially Boolean("false") being true. Exercise 3 is a real-world pattern — form data always comes as strings, and the receipt format is satisfying to build. Note C#'s use of decimal for money — worth emphasizing why double is a bad choice for financial calculations.

Summary

🎉 Key Takeaways

  • Explicit conversion is when you ask to convert: int(), Number(), int.Parse()
  • Implicit coercion is when the language converts for you — JavaScript does this aggressively, Python rarely, C# only for safe (widening) conversions
  • JavaScript's + operator favors strings (concatenation) while -, *, / favor numbers
  • User input is always a string — convert it before doing math
  • Use safe conversion patterns: Python's try/except, JavaScript's isNaN(), C#'s TryParse()
  • For money, use decimal in C# — float and double have rounding errors

🚀 What's Next?

You can now store data and convert between types. Next, we'll learn how to make decisions based on that data. Get ready for conditionals: if, else, and switch!

🎯 Quick Check

Question 1: What does "5" + 3 produce in JavaScript?

Question 2: What does "5" - 3 produce in JavaScript?

Question 3: In C#, which method safely converts a string to an integer without crashing?