Primitive Data Types:

NOTE: Kotlin doesn't have primitive types in the same way as Java does. However, it provides a set of classes that represent primitive data types under the hood. These classes are optimized for performance and behave like primitives, but they are still objects.

When to Use

  • Use Int, Long for whole numbers depending on size needed
  • Use Double for decimal numbers (Float for memory-constrained cases)
  • Use Boolean for true/false conditions
  • Use Char for single characters
Type What It Represents Example
Byte 8-bit signed integer (-128 to 127) val smallNumber: Byte = 100
Short 16-bit signed integer (-32,768 to 32,767) val mediumNumber: Short = 2000
Int 32-bit signed integer (about ±2 billion) val standardNumber = 1000000
Long 64-bit signed integer (very large range) val bigNumber = 3000000000L
Float 32-bit floating point number val decimalNumber = 3.14f
Double 64-bit floating point number val preciseDecimal = 3.14159265359
Char 16-bit Unicode character val letter = 'A'
Boolean true or false value val isActive = true

Object Data Types:

When to Use

  • Use String for text of any length
  • Use Array for fixed-size collections
  • Use List, Set, Map for flexible collections
Type What It Represents Example
String Text sequence val name = "Alex"
Array Fixed-size collection val numbers = arrayOf(1, 2, 3)
List Ordered collection val items = listOf("apple", "banana")
Set Unique elements collection val uniqueItems = setOf(1, 2, 3)
Map Key-value pairs val ages = mapOf("Alice" to 29)

Variables: Immutable and Mutable

When to Use

  • Use val by default (prefer immutability)
  • Use var only when a value needs to change
Type What It Does When to Use
val Defines an immutable (read-only) variable For values that should not change after initialization
var Defines a mutable variable that can be changed For values that need to be updated

Practical Examples

// ===========================================
// VARIABLE DECLARATION EXAMPLES
// ===========================================

// Example 1: Immutable variables (val)
// These variables cannot be changed after they're created
val immutableVal = "I cannot be changed"  // Immutable
val pi = 3.14159                          // Mathematical constant
val maxRetries = 3                         // Configuration value
val companyName = "TechCorp"               // Company name

println("Immutable variable: $immutableVal")
println("Pi: $pi")
println("Max retries: $maxRetries")
println("Company name: $companyName")

// Example 2: Mutable variables (var)
// These variables can be updated as needed
var mutableVar = "I can be changed"       // Mutable
var currentScore = 0                       // Game score that changes
var temperature = 72.5                     // Temperature that fluctuates
var isLoggedIn = false                     // Login status that changes

println("Mutable variable: $mutableVar")
println("Current score: $currentScore")
println("Temperature: $temperature")
println("Is logged in: $isLoggedIn")

// Example 3: Changing mutable variables
println("=== MUTABLE VARIABLE CHANGES ===")
println("Initial value: $mutableVar")
mutableVar = "See, I changed!"            // This works - updating the value
println("After change: $mutableVar")

currentScore = 100                         // Update score
println("Score updated to: $currentScore")

temperature = 85.2                         // Update temperature
println("Temperature changed to: $temperature")

isLoggedIn = true                          // Update login status
println("Login status: $isLoggedIn")

// Example 4: Attempting to change immutable variables (will cause errors)
// The following lines would cause compile errors:
// immutableVal = "Trying to change"      // Error: Val cannot be reassigned
// pi = 3.14                              // Error: Val cannot be reassigned
// maxRetries = 5                          // Error: Val cannot be reassigned

// Example 5: Type annotation examples
// You can explicitly specify the type, or let Kotlin infer it
val explicitInt: Int = 123              // Type annotation
val inferredInt = 123                   // Type inference (Kotlin knows it's Int)
val explicitString: String = "Hello"    // Type annotation
val inferredString = "World"            // Type inference (Kotlin knows it's String)

// Example 6: Real-world variable usage
println("\n=== REAL-WORLD VARIABLE USAGE ===")

// User profile information (immutable - shouldn't change)
val userId = "user_12345"
val dateOfBirth = "1990-05-15"
val email = "user@example.com"

// User session information (mutable - changes during use)
var loginAttempts = 0
var lastLoginTime = "2024-01-01 00:00:00"
var isOnline = false

// Game state variables (mutable - change during gameplay)
var playerHealth = 100
var playerLevel = 1
var experiencePoints = 0
var goldCoins = 50

println("User ID: $userId")
println("Login attempts: $loginAttempts")
println("Player health: $playerHealth")

// Update game state
playerHealth -= 20                       // Player took damage
experiencePoints += 150                  // Player gained experience
goldCoins += 25                         // Player found treasure

println("After updates:")
println("Player health: $playerHealth")
println("Experience points: $experiencePoints")
println("Gold coins: $goldCoins")

Nullability

When to Use

  • Use non-nullable types by default
  • Use nullable types only when a value might legitimately be absent
  • Use safe call operator when working with nullable values

Kotlin's type system helps prevent null pointer exceptions by making "nullability" explicit in the type system. By default, regular types cannot hold null:

Feature What It Does Example
Non-nullable type Cannot hold null values val name: String = "Alex"
Nullable type Can hold null values val name: String? = null
Safe call operator Safely calls methods on nullable types val length = name?.length
Elvis operator Provides default value for null cases val display = name ?: "Unknown"
Not-null assertion Forces treating as non-null (unsafe) val length = name!!.length

Practical Examples

// ===========================================
// NULLABILITY EXAMPLES
// ===========================================

// Example 1: Non-nullable vs nullable types
println("=== NULLABILITY COMPARISON ===")

// Non-nullable String - cannot be null
val nonNullableName: String = "John"
// val nonNullableName2: String = null  // This would cause a compile error!

// Nullable String - can be null
val nullableName: String? = null
val nullableName2: String? = "Jane"
val nullableName3: String? = "Bob"

println("Non-nullable name: $nonNullableName")
println("Nullable names: $nullableName, $nullableName2, $nullableName3")

// Example 2: Safe call operator (?.)
// This safely accesses properties without causing crashes
println("\n=== SAFE CALL OPERATOR ===")

val names = listOf(nullableName, nullableName2, nullableName3, "Alice")

for (name in names) {
    // Safe call - won't crash if name is null
    val length = name?.length
    println("Name: '$name', Length: $length")
    
    // You can also chain safe calls
    val uppercase = name?.uppercase()
    println("  Uppercase: $uppercase")
}

// Example 3: Elvis operator (?:)
// This provides default values when dealing with null
println("\n=== ELVIS OPERATOR ===")

val userInput1: String? = null
val userInput2: String? = "Hello World"
val userInput3: String? = ""

// Provide default values for null cases
val displayName1 = userInput1 ?: "Guest"
val displayName2 = userInput2 ?: "Guest"
val displayName3 = userInput3 ?: "Guest"  // Empty string is not null, so no default

println("User 1: '$userInput1' -> Display: '$displayName1'")
println("User 2: '$userInput2' -> Display: '$displayName2'")
println("User 3: '$userInput3' -> Display: '$displayName3'")

// Example 4: Safe conditional with if check
// This is a way to handle null values safely
println("\n=== SAFE CONDITIONAL CHECKS ===")

val potentialName: String? = "Alice"

if (potentialName != null) {
    // Within this block, Kotlin knows potentialName is not null
    val nameLength = potentialName.length  // Smart cast - no need for safe call
    val uppercaseName = potentialName.uppercase()
    println("Name: $potentialName, Length: $nameLength, Uppercase: $uppercaseName")
} else {
    println("No name provided")
}


// Example 5: Not-null assertion (!!) - Use with caution!
// This forces Kotlin to treat a nullable value as non-null
println("\n=== NOT-NULL ASSERTION (DANGEROUS) ===")

val definitelyNotNull: String? = "Safe to use"
val mightBeNull: String? = null

try {
    // This is safe because we know the value is not null
    val length1 = definitelyNotNull!!.length
    println("Safe assertion: $length1")
    
    // This will crash with NullPointerException!
    val length2 = mightBeNull!!.length
    println("This will never print: $length2")
} catch (e: NullPointerException) {
    println("Caught NullPointerException: ${e.message}")
}

// Example 6: Real-world nullability scenarios
println("\n=== REAL-WORLD NULLABILITY SCENARIOS ===")

// Simulate user input that might be missing
val userAge: String? = "25"
val userPhone: String? = null
val userAddress: String? = ""

// Process user information safely
val age = userAge?.toIntOrNull() ?: 0
val phone = userPhone ?: "No phone provided"
val address = if (userAddress.isNullOrEmpty()) "No address provided" else userAddress

println("User Profile:")
println("  Age: $age")
println("  Phone: $phone")
println("  Address: $address")

// Example 7: Working with collections that might contain null
println("\n=== NULLABLE COLLECTIONS ===")

val mixedList: List = listOf("Alice", null, "Bob", null, "Charlie")

// Filter out null values
val nonNullNames = mixedList.filterNotNull()
println("All names (including nulls): $mixedList")
println("Non-null names only: $nonNullNames")

// Count null vs non-null values
val nullCount = mixedList.count { it == null }
val nonNullCount = mixedList.count { it != null }
println("Null values: $nullCount, Non-null values: $nonNullCount")

Let Function

When to Use

  • When you want to perform operations only if a value is not null
  • To avoid repeating a variable name multiple times
  • To limit the scope of variables
Pattern What It Does When to Use
value?.let { } Executes block only if value is not null For null-safe operations on optional values
value?.let { } ?: defaultValue Executes block or returns default if null For transforming values with a fallback

Practical Examples

// ===========================================
// LET FUNCTION EXAMPLES
// ===========================================

// Example 1: Basic usage with non-null value
// The let function executes the block and returns the result
println("=== BASIC LET USAGE ===")
val name = "Alice"
val result = name.let {
    println("Processing name: $it")
    it.length  // Returns the length of 'name'
}
println("Result: $result")

// Example 2: Null-safe usage with let
// This is the most common use case for let
println("\n=== NULL-SAFE LET USAGE ===")
val nullableName: String? = "Bob"
val nameLength = nullableName?.let {
    println("Name is: $it")
    it.length  // This block only executes if nullableName is not null
} ?: 0
println("Name length: $nameLength")

// Example 3: Let with null value
// When the value is null, the let block is skipped
println("\n=== LET WITH NULL VALUE ===")
val emptyName: String? = null
val length = emptyName?.let {
    it.length  // This block is skipped if emptyName is null
} ?: 0
println("Length of null name: $length")

// Example 4: Let for data transformation
// Let is great for transforming data safely
println("\n=== LET FOR DATA TRANSFORMATION ===")
val userInput: String? = "  hello world  "

val processedInput = userInput?.let {
    it.trim()           // Remove whitespace
        .lowercase()     // Convert to lowercase
        .replace(" ", "_") // Replace spaces with underscores
} ?: "default_value"

println("Original input: '$userInput'")
println("Processed input: '$processedInput'")

// Example 5: Let with multiple operations
// You can perform complex operations within the let block
println("\n=== LET WITH MULTIPLE OPERATIONS ===")
val email: String? = "user@example.com"

val userInfo = email?.let {
    val username = it.substringBefore('@')
    val domain = it.substringAfter('@')
    val isValid = it.contains('@') && it.contains('.')
    
    //mapOf is a function that creates a map from the given key-value pairs
    mapOf(
        "email" to it,
        "username" to username,
        "domain" to domain,
        "isValid" to isValid
    )
} ?: mapOf("error" to "No email provided")

println("User info: $userInfo")

// Example 6: Let for object initialization
// Let can be used to safely initialize objects
println("\n=== LET FOR OBJECT INITIALIZATION ===")
val configString: String? = "name=John,age=25,city=New York"

val config = configString?.let {
    val pairs = it.split(",")
    val configMap = mutableMapOf()
    
    for (pair in pairs) {
        val (key, value) = pair.split("=")
        configMap[key.trim()] = value.trim()
    }
    
    configMap
} ?: emptyMap()

println("Configuration: $config")

// Example 7: Let with chaining
// You can chain let calls for complex operations
println("\n=== LET WITH CHAINING ===")
val numberString: String? = "42"

val result2 = numberString?.let { str ->
    str.toIntOrNull()?.let { num ->
        if (num > 0) {
            num * 2
        } else {
            num * -1
        }
    }
} ?: 0

println("Original string: '$numberString'")
println("Processed result: $result2")

// Example 8: Real-world let usage scenarios
println("\n=== REAL-WORLD LET SCENARIOS ===")

// Scenario 1: Processing user input
val userInput2: String? = "  123.45  "
val processedNumber = userInput2?.let {
    it.trim().toDoubleOrNull()
}?.let { num ->
    if (num > 0) num else 0.0
} ?: 0.0

println("User input: '$userInput2'")
println("Processed number: $processedNumber")

// Scenario 2: Database query result
val queryResult: String? = "user_id=123,name=Alice,email=alice@example.com"
val user = queryResult?.let {
    val parts = it.split(",")
    val userMap = parts.associate { part ->
        val (key, value) = part.split("=")
        key to value
    }
    userMap
} ?: emptyMap()

println("Query result: '$queryResult'")
println("Parsed user: $user")

// Scenario 3: API response processing
val apiResponse: String? = """{"status": "success", "data": {"name": "Bob", "age": 30}}"""
val userName = apiResponse?.let {
    // In a real app, you'd use a JSON parser
    if (it.contains("\"name\"")) {
        val nameStart = it.indexOf("\"name\": \"") + 9
        val nameEnd = it.indexOf("\"", nameStart)
        it.substring(nameStart, nameEnd)
    } else {
        null
    }
} ?: "Unknown User"

println("API response: '$apiResponse'")
println("Extracted name: $userName")

Type Casting

Type casting is the process of converting a value from one type to another. Kotlin provides two operators for type casting: as and as?.

as is the unsafe cast operator, which throws an exception if the cast fails. as? is the safe cast operator, which returns null if the cast fails.

When to Use

  • When working with heterogeneous collections
  • When dealing with inheritance hierarchies
  • When interacting with less type-safe Java code
Operator What It Does When to Use
is Checks if an object is of a specific type Before performing operations specific to a type
as Unsafe cast (throws exception if fails) When you're certain of the type
as? Safe cast (returns null if fails) When the cast might fail

Practical Examples

// ===========================================
// TYPE CASTING EXAMPLES
// ===========================================

// Example 1: Type checking with 'is' operator
// This is the safest way to check types
println("=== TYPE CHECKING WITH 'IS' ===")
val value: Any = "Hello World"

if (value is String) {
    // Smart cast: value is treated as String inside this block
    println("Value is a String: '$value'")
    println("Length: ${value.length}")
    println("Uppercase: ${value.uppercase()}")
    
    // You can also use string-specific methods
    val words = value.split(" ")
    println("Number of words: ${words.size}")
} else {
    println("Value is not a String")
}

// Example 2: Type checking with different types
// You can check for multiple types
println("\n=== CHECKING MULTIPLE TYPES ===")
val mixedList: List<Any> = listOf("Hello", 42, 3.14, true, 'A')

for (item in mixedList) {
    when (item) {
        is String -> {
            println("String: '$item' (length: ${item.length})")
        }
        is Int -> {
            println("Integer: $item (doubled: ${item * 2})")
        }
        is Double -> {
            println("Double: $item (rounded: ${item.toInt()})")
        }
        is Boolean -> {
            println("Boolean: $item (opposite: ${!item})")
        }
        is Char -> {
            println("Character: '$item' (code: ${item.code})")
        }
        else -> {
            println("Unknown type: ${item::class.simpleName}")
        }
    }
}

// Example 3: Unsafe cast with 'as' operator
// This throws an exception if the cast fails
println("\n=== UNSAFE CAST WITH 'AS' ===")

val obj: Any = "I'm a string"
try {
    val str: String = obj as String  // Works because obj is actually a String
    println("Successfully cast to String: '$str'")
    
    // This would throw ClassCastException
    val willCrash: Int = obj as Int
    println("This will never print: $willCrash")
} catch (e: ClassCastException) {
    println("Cast failed: ${e.message}")
}

// Example 4: Safe cast with 'as?' operator
// This returns null if the cast fails instead of throwing an exception
println("\n=== SAFE CAST WITH 'AS?' ===")

val anyValue: Any = 123
val safeString: String? = anyValue as? String  // Will be null since anyValue is not a String
val safeInt: Int? = anyValue as? Int           // Will be 123

println("Safe cast to String: $safeString")
println("Safe cast to Int: $safeInt")

// Example 5: Combining safe cast with Elvis operator
// This provides a default value when the cast fails
println("\n=== SAFE CAST WITH ELVIS OPERATOR ===")

val mixedData: List<Any> = listOf("Hello", 42, 3.14, "World", 100)

for (item in mixedData) {
    val stringValue = (item as? String) ?: "Not a string"
    val intValue = (item as? Int) ?: 0
    val doubleValue = (item as? Double) ?: 0.0
    
    println("Item: $item")
    println("  As String: '$stringValue'")
    println("  As Int: $intValue")
    println("  As Double: $doubleValue")
    println()
}

// Example 6: Type casting in inheritance scenarios
// This is common when working with class hierarchies
println("\n=== TYPE CASTING WITH INHERITANCE ===")

// Simulate a class hierarchy
open class Animal(val name: String)
class Dog(name: String, val breed: String) : Animal(name)
class Cat(name: String, val color: String) : Animal(name)

val animals: List<Animal> = listOf(
    Dog("Buddy", "Golden Retriever"),
    Cat("Whiskers", "Orange"),
    Dog("Max", "Labrador"),
    Cat("Shadow", "Black")
)

for (animal in animals) {
    when (animal) {
        is Dog -> {
            println("Dog: ${animal.name} (Breed: ${animal.breed})")
            // You can access Dog-specific properties
        }
        is Cat -> {
            println("Cat: ${animal.name} (Color: ${animal.color})")
            // You can access Cat-specific properties
        }
        else -> {
            println("Unknown animal: ${animal.name}")
        }
    }
}

// Example 7: Real-world type casting scenarios
println("\n=== REAL-WORLD TYPE CASTING SCENARIOS ===")

// Scenario 1: Processing API responses
val apiResponse: Any = mapOf(
    "status" to "success",
    "data" to mapOf(
        "users" to listOf(
            mapOf("id" to 1, "name" to "Alice"),
            mapOf("id" to 2, "name" to "Bob")
        )
    )
)

// Safely extract user data
val users = (apiResponse as? Map<*, *>)?.let { response ->
    (response["data"] as? Map<*, *>)?.let { data ->
        (data["users"] as? List<*>)?.let { userList ->
            userList.mapNotNull { user ->
                (user as? Map<*, *>)?.let { userMap ->
                    val id = userMap["id"] as? Int
                    val name = userMap["name"] as? String
                    if (id != null && name != null) {
                        "User $id: $name"
                    } else null
                }
            }
        }
    }
} ?: emptyList()

println("API Response: $apiResponse")
println("Extracted users: $users")

// Scenario 2: Database result processing
val dbResult: Any = listOf(
    mapOf("id" to 1, "name" to "Product A", "price" to 29.99),
    mapOf("id" to 2, "name" to "Product B", "price" to 49.99),
    mapOf("id" to 3, "name" to "Product C", "price" to 19.99)
)

val products = (dbResult as? List<*>)?.mapNotNull { row ->
    (row as? Map<*, *>)?.let { product ->
        val id = product["id"] as? Int
        val name = product["name"] as? String
        val price = product["price"] as? Double
        
        if (id != null && name != null && price != null) {
            "ID: $id, Name: $name, Price: $${String.format("%.2f", price)}"
        } else null
    }
} ?: emptyList()

println("Database Result: $dbResult")
println("Processed products: $products")

String Formatting

When to Use

  • When precise control over number formatting is needed
  • When working with tabular data presentation
  • When creating complex string patterns
Format What It Does Example
%s String placeholder String.format("Hello, %s", name)
%d Integer placeholder String.format("Count: %d", count)
%.2f Float with 2 decimal places String.format("Price: $%.2f", price)
${} String template (Kotlin preferred) "Hello, ${name.capitalize()}"

Practical Examples

// ===========================================
// STRING FORMATTING EXAMPLES
// ===========================================

// Example 1: Simple string templates (Kotlin's preferred way)
// This is the most readable and flexible approach
println("=== SIMPLE STRING TEMPLATES ===")
val name = "John"
val age = 25
val simple = "Hello, $name! You are $age years old."
println(simple)

// Example 2: Using expressions in templates
// You can put any valid Kotlin expression inside ${}
println("\n=== EXPRESSIONS IN STRING TEMPLATES ===")
val score = 85
val message = "${name.uppercase()} scored $score points, which is ${if (score >= 80) "excellent" else "good"}!"
println(message)

// Example 3: Complex expressions in templates
// You can perform calculations and method calls
println("\n=== COMPLEX EXPRESSIONS IN TEMPLATES ===")
val price = 29.99
val quantity = 3
val total = price * quantity
val discount = if (quantity >= 5) 0.10 else 0.0
val finalTotal = total * (1 - discount)

val receipt = """
Receipt for $name:
  Item price: $${String.format("%.2f", price)}
  Quantity: $quantity
  Subtotal: $${String.format("%.2f", total)}
  Discount: ${(discount * 100).toInt()}%
  Final total: $${String.format("%.2f", finalTotal)}
""".trimIndent()

println(receipt)

// Example 4: String.format for precise formatting
// Use this when you need specific formatting control
println("\n=== STRING.FORMAT EXAMPLES ===")
val productName = "Laptop"
val productPrice = 999.99
val productQuantity = 2

val formatted = String.format("Product: %s, Price: $%.2f, Quantity: %d", 
                             productName, productPrice, productQuantity)
println(formatted)

// Example 5: Multiple values in one format string
// This is useful for creating structured output
println("\n=== MULTIPLE VALUES IN FORMAT STRING ===")
val customerName = "Alice Smith"
val orderNumber = "ORD-2024-001"
val orderDate = "2024-01-15"
val orderTotal = 149.99

val orderSummary = String.format("""
Order Summary:
  Customer: %s
  Order #: %s
  Date: %s
  Total: $%.2f
""".trimIndent(), customerName, orderNumber, orderDate, orderTotal)

println(orderSummary)

// Example 6: Number formatting with different precision
// Control decimal places and number formatting
println("\n=== NUMBER FORMATTING ===")
val pi = 3.14159265359
val largeNumber = 1234567.89
val percentage = 0.1234

println("Pi to 2 decimal places: ${String.format("%.2f", pi)}")
println("Pi to 4 decimal places: ${String.format("%.4f", pi)}")
println("Large number with commas: ${String.format("%,.2f", largeNumber)}")
println("Percentage: ${String.format("%.2f%%", percentage * 100)}")

// Example 7: Alignment and spacing
// Control how text is positioned
println("\n=== TEXT ALIGNMENT AND SPACING ===")
val items = listOf("Apple", "Banana", "Cherry", "Date")
val prices = listOf(1.99, 0.99, 3.99, 2.49)

println("Product List:")
for (i in items.indices) {
    val formattedLine = String.format("%-10s $%6.2f", items[i], prices[i])
    println(formattedLine)
}

// Example 8: Real-world formatting scenarios
println("\n=== REAL-WORLD FORMATTING SCENARIOS ===")

// Scenario 1: Financial report
val companyName = "TechCorp Inc."
val revenue = 1250000.50
val expenses = 875000.25
val profit = revenue - expenses
val profitMargin = (profit / revenue) * 100

val financialReport = String.format("""
FINANCIAL REPORT - %s
==========================================
Revenue:     $%,.2f
Expenses:    $%,.2f
Profit:      $%,.2f
Margin:      %.2f%%
""".trimIndent(), companyName, revenue, expenses, profit, profitMargin)

println(financialReport)

// Scenario 2: User profile display
val userName = "john_doe"
val userEmail = "john.doe@example.com"
val userAge = 28
val userScore = 95.5
val isActive = true

val userProfile = """
USER PROFILE
============
Username:    $userName
Email:       $userEmail
Age:         $userAge
Score:       ${String.format("%.1f", userScore)}%
Status:      ${if (isActive) "Active" else "Inactive"}
""".trimIndent()

println(userProfile)

// Scenario 3: Table formatting
val students = listOf(
    Triple("Alice", 95, "A"),
    Triple("Bob", 87, "B"),
    Triple("Charlie", 92, "A"),
    Triple("Diana", 78, "C")
)

println("STUDENT GRADES")
println("==============")
println(String.format("%-10s %-8s %-6s", "Name", "Score", "Grade"))
println("---------- -------- ------")

for ((name, score, grade) in students) {
    val formattedRow = String.format("%-10s %-8d %-6s", name, score, grade)
    println(formattedRow)
}