🔄 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:
(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.
- Store a Fahrenheit temperature as a string (simulating user input):
"98.6" - Convert it to a number
- Apply the formula:
celsius = (fahrenheit - 32) × 5 / 9 - 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.
"10" + 5"10" - 5"10" * "2"true + 1"hello" - 1"" + 0Boolean("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.
- Start with these string values (simulating form input): item name =
"Widget", price ="12.50", quantity ="3", tax rate ="0.08" - Convert to the appropriate types
- Calculate subtotal, tax amount, and total
- 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'sisNaN(), C#'sTryParse() - For money, use
decimalin C# —floatanddoublehave 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?