Short Excerpts

Short Insights on Previously Covered Random Topics

Git Interview Questions and Answers

Mastering Git: The Most Common Git Interview Questions and Answers

Git is one of the most widely used version control systems, and knowing how to use it efficiently is essential for developers. If you’re preparing for a job interview, expect Git-related questions to come up, whether you’re a beginner or an experienced professional. In this detailed guide, we’ll explore the most commonly asked Git interview questions along with in-depth explanations and accurate answers.

Introduction to Git

What is Git?

Git is a distributed version control system (DVCS) that allows multiple developers to collaborate on a project by tracking changes in files. Unlike traditional version control systems, Git enables decentralized development, making it possible to work offline and merge changes seamlessly.

What is the Difference Between Git and GitHub?

Many beginners confuse Git with GitHub, but they serve different purposes:

  • Git: A version control system that runs locally on a developer’s computer.
  • GitHub: A cloud-based hosting platform that allows developers to store, share, and collaborate on Git repositories.

Git Basics

How to Check the Status of a Git Repository?

To check the status of files in your repository, use:

Bash
git status


// Note - Selected File format is Bash, not ideal but it's ok 

This command provides information on modified, staged, and untracked files.

How to Initialize a New Git Repository?

To create a new Git repository in a project directory, use:

Bash
git init

This creates a .git directory where Git stores version control information.

How to Clone a Remote Repository?

To copy a remote repository to your local machine, use:

Bash
git clone <repository-url>

This command downloads the entire repository and history.

Branching and Merging

How to Create and Switch to a New Branch?

To create a new branch and switch to it, use:

Bash
git checkout -b <branch-name>

Alternatively, you can create and switch separately:

Bash
git branch <branch-name>
 git checkout <branch-name>

How to Merge Branches?

Switch to the branch you want to merge changes into, then run:

Bash
git commit -m "Descriptive commit message"

If there are conflicts, Git will prompt you to resolve them before completing the merge.

What is a Merge Conflict and How to Resolve It?

A merge conflict occurs when two branches modify the same part of a file differently. To resolve:

  1. Open the conflicted file.
  2. Manually edit the conflicting sections.
  3. Run:
Bash
git add <file> git commit -m "Resolved merge conflict"

Committing Changes

How to Add Files to the Staging Area?

To stage specific files:

Bash
git add <file-name>

To stage all changes:

Bash
git add .

How to Commit Changes?

To save staged changes:

Bash
git commit -m "Descriptive commit message"

How to Undo the Last Commit?

  • Keep changes but remove commit:
Bash
git reset --soft HEAD~1
  • Remove commit and changes:
Bash
git reset --hard HEAD~1

Remote Collaboration

How to Push Changes to a Remote Repository?

Bash
git push origin <branch-name>

How to Pull the Latest Changes from a Remote Repository?

Bash
git pull origin <branch-name>

How to Create a Pull Request?

  1. Push changes to GitHub:
Bash
git push origin <branch-name>
  1. Go to GitHub, open the repository, and click “New Pull Request.”
  2. Select branches and submit the PR.

How to Delete a Branch?

  • Delete a local branch:
Bash
git branch -d <branch-name>
  • Delete a remote branch:
Bash
git push origin --delete <branch-name>

Advanced Git

Difference Between git merge and git rebase

  • git merge: Combines two branches and keeps the commit history.
  • git rebase: Moves commits from one branch on top of another, creating a linear history.

How to Stash Changes in Git?

To temporarily save changes without committing:

Bash
git stash

To reapply stashed changes:

Bash
git stash apply

To remove a stash:

Bash
git stash drop

How to Revert a Commit Without Deleting History?

Bash
git revert <commit-hash>

This creates a new commit that undoes the previous changes.

How to View Commit History?

Bash
git log

Bonus

What is .gitignore?

.gitignore file specifies files and directories that should be ignored by Git. 

Bash
node_modules/
*.log
.env

What is git cherry-pick?

This command applies a specific commit from one branch to another:

Bash
git cherry-pick <commit-hash>

What is git bisect?

A tool to find a commit that introduced a bug:

Bash
git bisect start
 git bisect bad
 git bisect good <commit-hash>

How to Change the Last Commit Message?

Bash
git commit --amend -m "New commit message"

What is git reflog Used For?

Tracks all changes in HEAD, even undone ones:

Bash
git reflog

Conclusion

Mastering Git is essential for every developer. Whether you’re preparing for an interview or improving your workflow, understanding Git commands and best practices will make you a more efficient and reliable team member. The best way to solidify your Git knowledge is through hands-on practice, so try these commands in a real repository to gain confidence..!

By following this guide, you’ll be well-prepared to answer Git interview questions and showcase your version control expertise.

Device Mirroring

Why You Can’t Interact with Device Mirroring in Android Studio Meerkat (And How to Fix It)

With the arrival of Android Studio Meerkat, Google has brought some exciting quality-of-life improvements for Android developers — including Device Mirroring, a sleek way to display your physical device screen directly inside the IDE.

But there’s one frustrating hiccup many developers are running into:

“The screen mirrors just fine, but I can’t interact with it! No clicks, no touches — nothing works.”

If that’s happening to you, don’t worry. You’re not alone, and this post breaks down why it’s happening, what you can (and can’t) do about it, and the best alternatives if you need full input control.

Device Mirroring in Meerkat: What’s the Deal?

Device Mirroring in Android Studio Meerkat lets you see your physical Android device’s screen directly in the IDE, without any third-party tools or extra setup. It’s a welcome addition, especially if you frequently run and test builds on physical devices.

However, many developers are discovering a limitation: it’s currently passive mirroring only — you can see your device screen, but you can’t interact with it via the mouse or keyboard in many environments.

This limitation is not a bug in your setup — it’s a limitation in the feature itself (for now).

Why Interaction Doesn’t Work

Here’s why you’re unable to touch, swipe, or type into the mirrored screen:

1. ADB Permissions May Be Misconfigured

Even if mirroring appears to work, input control requires proper authorization between your device and ADB.

Fix:
  • Disconnect and reconnect your device.
  • On your Android device, go to: Settings → Developer Options → Revoke USB debugging authorizations
  • Reconnect the device and tap “Allow” when prompted.

This ensures ADB has full access.

2. Device Mirroring is Currently Read-Only in Some Builds

Not all versions of Android Studio Meerkat allow full interaction. Even though the screen is mirrored, Google is still rolling out input support gradually, and many stable builds do not yet include full touch or input simulation.

In short: You’re not doing anything wrong — this feature just isn’t fully baked yet in some builds.

How to Work Around It (and Actually Interact)

If you absolutely need to interact with your device screen from your computer, there’s a better tool made for that job: scrcpy.

Solution: Use scrcpy for Full Interaction

scrcpy is an open-source tool that mirrors your Android device screen with full keyboard and mouse input support, including:

  • Clicks and gestures
  • Text input
  • Copy/paste between device and computer
  • Drag-and-drop APK installation
  • Even wireless mirroring

It’s fast, lightweight, and works beautifully.

Installation (Pick Your Platform):

On macOS:

Bash
brew install scrcpy

On Ubuntu/Debian:

Bash
sudo apt install scrcpy

On Windows (with Chocolatey):

Bash
choco install scrcpy

Running It:

Just plug in your device and type:

Bash
scrcpy

Boom. Fully interactive Android device on your screen.

Extra Tips to Make Sure Mirroring Works Smoothly

Even if you stick with Android Studio’s built-in mirroring (and wait for interaction support), here are a few things to check:

1. Update Android Studio Meerkat

Make sure you’re running the latest build — Google is rolling out changes rapidly.

  • Go to Help → Check for Updates (on macOS: Android Studio → Check for Updates)
  • Or download the latest version directly from: developer.android.com/studio

2. Revisit Developer Options on Device

  • USB debugging must be enabled
  • Consider disabling “Restrict USB access” (available on some newer Android versions)
  • Turn off features like Pointer location and Show touches if they interfere

Frequently Asked Question: Is Device Mirroring Meant to Replace scrcpy?

Not yet. Android Studio’s Device Mirroring is still evolving. It’s great for previewing, but not quite ready to replace scrcpy if you need:

  • Full touch simulation
  • Input from mouse and keyboard
  • Drag-and-drop support

Google may eventually match those features, but scrcpy is the gold standard right now.

Conclusion

Device Mirroring in Android Studio Meerkat is a solid step forward, but it’s not yet a complete solution for interactive testing. If you’re seeing the screen but can’t touch it, that’s not your fault — it’s just where the feature currently stands.

In the meantime, scrcpy is your best friend for full control, and it works side-by-side with Android Studio just fine.

Keep your tools updated, watch for changes in Canary builds, and you’ll be the first to benefit when full interaction support lands.

Capturing Final Variable in Kotlin Lambda

How Capturing Final Variable in Kotlin Lambda Works Behind the Scenes

Kotlin is a modern programming language that embraces functional programming principles, and lambdas are an essential part of it. When working with lambdas, you might have noticed that they can access variables from their surrounding scope. But how does capturing final variables in Kotlin lambda actually work behind the scenes?

In this blog post, we’ll take a deep dive into this concept and explore the internal workings of final variable capture in Kotlin lambdas.

Understanding Capturing Final Variable in Kotlin Lambdas

Before diving into the technical details, let’s first understand what variable capturing means. When you define a lambda inside a function or block of code, it can access variables that are declared outside of its own scope.

Kotlin follows Java’s concept of effectively final variables, meaning that variables captured in lambdas must be final (i.e., immutable) or treated as final.

Kotlin
fun main() {
    val message = "Hello, softAai..!"   // Final variable
    
    val printMessage = {
        println(message) // Capturing 'message' inside lambda
    }
    
    printMessage() // Output: Hello, softAai..!
}

Here, message is captured inside the lambda printMessage. But how does Kotlin manage this internally? Let’s find out.

What Happens Behind the Scenes?

When a lambda captures a variable, Kotlin internally converts the lambda into an anonymous class that holds a reference to the captured variable. Let’s see what’s happening at a deeper level.

Lambdas as Anonymous Classes

Under the hood, Kotlin compiles lambdas into anonymous classes that implement the Function interfaces (such as Function0, Function1, etc.).

When a lambda captures a variable from an enclosing scope, the Kotlin compiler transforms the lambda into a class where the captured variable becomes a field of that class.

Decompiling to Java Code

To see what happens behind the scenes, let’s decompile the Kotlin code above into Java:

Note: This is a simplified and understandable decompiled Java code. You might see a slightly different variant when you decompile it on your end.

Kotlin
public final class MainKt {
   public static void main() {
      final String message = "Hello, softAai..!";
      Function0 printMessage = new Function0() {
         public void invoke() {
            System.out.println(message);
         }
      };
      printMessage.invoke();
   }
}

As you can see, the message variable is stored as a final variable inside an anonymous class implementing Function0, which allows it to be used within the lambda.

Let’s go through one more example.

Kotlin
fun main() {
    val number = 10
    
    val multiply = { x: Int -> x * number }
    
    println(multiply(5)) // Output: 50
}

In simplified and understandable Java bytecode, Kotlin translates this lambda into something like this:

Kotlin
public final class MainKt {
    public static void main() {
        final int number = 10;
        
        Function1<Integer, Integer> multiply = new Function1<Integer, Integer>() {
            @Override
            public Integer invoke(Integer x) {
                Intrinsics.checkNotNullParameter(x, "x");
                return x * number;
            }
        };

        System.out.println(multiply.invoke(5)); // Output: 50
    }
}

Here,

  • Since number is used inside the lambda, it is captured as a final local variable.
  • The lambda is converted into an anonymous inner class implementing Function1<Integer, Integer>. invoke() method is overridden to perform the multiplication.
  • This is a Kotlin runtime null-safety check ensuring x is not null.

Why Kotlin Uses This Approach

As seen in the Java equivalent, number is stored as a final field inside the generated class. This ensures that even though the original variable exists in a different scope, it remains accessible within the lambda.

Effectively Final Restriction

One crucial aspect of capturing final variables in Kotlin lambda is that the variable must be effectively final, meaning it cannot change after being assigned.

The choice to enforce capturing only final (or effectively final) variables comes with several benefits:

  • Reduced Risk of Race Conditions: Since captured variables cannot be reassigned, the risk of race conditions is lower. However, thread safety is not guaranteed if the variable refers to a mutable object.
  • Performance Optimizations: Immutable variables lead to more predictable execution and allow the compiler to optimize bytecode efficiently.
  • Cleaner and Safer Code: Prevents accidental mutations and side effects, making the code easier to reason about.

Explore the complete details here: [Main Article URL]

Conclusion

Capturing final variables in Kotlin lambdas works by converting the lambda into an anonymous class where the captured variables are stored as immutable fields. This mechanism ensures safety, performance, and predictability in your code. Understanding how capturing final variables in Kotlin lambda works behind the scenes can help you write more efficient and bug-free Kotlin code.

const vs val

Const vs Val in Kotlin: Understanding the Key Differences

When working with Kotlin, you’ll often come across two keywords for defining immutable variables: const and val. While both ensure that the value cannot be reassigned, they serve different purposes and operate under distinct rules. In this guide, we’ll take an in-depth look at const vs val, their key differences, use cases, and best practices.

What is const in Kotlin?

The const keyword in Kotlin is used to declare compile-time constants. This means that the value is known and assigned at compile time, rather than at runtime.

Characteristics of const:

  • Declared using const val (not const var since it must be immutable).
  • Can only be assigned primitives (Int, Double, Boolean, String, etc.).
  • Cannot be assigned a function call or any computed value.
  • Must be declared at the top level, inside an object, or within a companion object.

Example of const Usage:

Kotlin
const val APP_NAME = "MyKotlinApp"
const val MAX_RETRY_COUNT = 3

Here, APP_NAME and MAX_RETRY_COUNT are known at compile time and will never change throughout the application lifecycle.

Where You Can Use const

  • Inside top-level declarations (outside any function or class).
  • Inside an object or companion object.

Example in an object:

Kotlin
object Config {
    const val API_ENDPOINT = "https://api.softaai.com"
}

This approach ensures that constants are easily accessible and prevent unnecessary object instantiation.

What is val in Kotlin?

The val keyword is used for declaring runtime immutable variables. This means that once a val variable is assigned, it cannot be changed, but its value is determined at runtime.

Characteristics of val:

  • Assigned a value at runtime, not compile-time.
  • Can hold any type of object, including lists, function calls, and class instances.
  • Can be declared anywhere, including inside functions.
  • Cannot be reassigned, but the referenced object may be mutable, like MutableList (the content of that object can change).

Example of val Usage:

Kotlin
val timestamp = System.currentTimeMillis() // Assigned at runtime
val user = User("Amol Pawar") // Holding an object instance

Here, timestamp gets a value at runtime when the function executes.

Where You Can Use val

  • Inside functions
  • Inside classes and objects
  • As local variables
  • As properties of a class

Example Inside a Function:

Kotlin
fun fetchData() {
    val currentTime = System.currentTimeMillis()
    println("Data fetched at: $currentTime")
}

currentTime gets assigned a value dynamically when the function runs.

Key Differences Between const and val

Featureconstval
Evaluation TimeCompile-timeRuntime
Allowed Data TypesOnly primitives (String, Int, etc.)Any type (objects, lists, function results, etc.)
Can Hold Function Calls?NoYes
Can Be Declared Inside Functions?NoYes
Where Can It Be Declared?Top-level, object, companion objectAnywhere
Immutable?YesYes (but object properties may be mutable)

When to Use const vs val?

Use const when:

  • The value never changes and is known at compile time.
  • You need a global constant (e.g., API keys, app configurations).
  • The value is a primitive or string.

Use val when:

  • The value is immutable but assigned at runtime.
  • You need to store computed values like timestamps or function results.
  • You are working with objects, lists, or complex types.

Practical Examples and Use Cases

Using const for App Configuration

Kotlin
object Config {
    const val BASE_URL = "https://api.softaai.com"
    const val DEFAULT_TIMEOUT = 5000
}

Here, BASE_URL and DEFAULT_TIMEOUT are known constants that never change.

Using val for Mutable Objects

Kotlin
fun getUsers(): List<String> {
    val users = listOf("Amol", "Baban", "Chetan")
    return users
}

users is immutable, but it holds a list that’s assigned at runtime.

Using val in Jetpack Compose

Kotlin
@Composable
fun Greeting(name: String) {
    val greetingMessage = "Hello, $name!" // Evaluated at runtime
    Text(text = greetingMessage)
}

greetingMessage is assigned dynamically based on the name parameter.

Conclusion

Understanding the difference between const and val is crucial for writing efficient and well-structured Kotlin code. While both ensure immutability, const is for compile-time constants, whereas val is for runtime immutable variables.

By applying the right choice in different scenarios, you can optimize your Kotlin applications for better readability, performance, and maintainability.

Custom CoroutineScope

Creating and Managing a Custom CoroutineScope in Kotlin

Kotlin’s coroutines make asynchronous programming easier and more efficient. However, managing coroutines properly requires an understanding of CoroutineScope. Without it, your application might create uncontrolled coroutines, leading to memory leaks, unexpected behavior, or inefficient resource usage.

In this blog, we’ll take a deep dive CoroutineScope in Kotlin, explore how to create a custom CoroutineScope, and discuss best practices for managing coroutines effectively.

Understanding CoroutineScope in Kotlin

A CoroutineScope defines the lifecycle and context for coroutines. Every coroutine launched inside a CoroutineScope inherits its CoroutineContext, which includes elements like a Job for tracking execution and a CoroutineDispatcher for thread management.

Why Is CoroutineScope Important?

  • Prevents memory leaks: Ensures that coroutines are properly canceled when no longer needed.
  • Manages structured concurrency: Helps group coroutines so they can be controlled together.
  • Defines execution context: Assigns dispatcher (e.g., Dispatchers.IO, Dispatchers.Main) for coroutines.

Using predefined scopes like viewModelScope (Android ViewModel) or lifecycleScope (Android components) is often recommended. However, in some cases, you may need to create a custom CoroutineScope.

Creating a Custom CoroutineScope in Kotlin

You can define a custom CoroutineScope by implementing the CoroutineScope interface and specifying a coroutineContext

Kotlin
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.*

class MyCustomScope : CoroutineScope {
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job // Assigning a background dispatcher

    fun launchTask() {
        launch(coroutineContext) { // Explicitly using coroutineContext
            println("Coroutine started on: $coroutineContext")
            delay(1000)
            println("Task completed")
        }
    }

    fun cancelScope() {
        println("Cancelling scope with context: $coroutineContext")
        job.cancel() // Cancels all coroutines in this scope
    }
}

fun main() {
    val myScope = MyCustomScope()
    myScope.launchTask()

    runBlocking { delay(1500) } // Ensures coroutine runs before program exits
    myScope.cancelScope() // Cleanup
}



/////////////////////// OUTPUT ///////////////////////////////

Coroutine started on: [StandaloneCoroutine{Active}@7822150c, Dispatchers.IO]
Task completed
Cancelling scope with context: [JobImpl{Active}@711f39f9, Dispatchers.IO]

Here,

MyCustomScope implements CoroutineScope, requiring it to define coroutineContext.

Manages Coroutine Lifecycle:

  • private val job = Job() creates a root job that manages all launched coroutines.
  • override val coroutineContext provides a combination of Dispatchers.IO (background execution) and Job (coroutine tracking).

Explicitly Uses coroutineContext:

  • launch(coroutineContext) { ... } ensures that the correct context is used when launching coroutines. 
  • Logging println("Coroutine started on: $coroutineContext") helps verify execution.

Handles Cleanup:

  • cancelScope() calls job.cancel(), terminating all active coroutines.
  • This prevents memory leaks and ensures proper resource cleanup.

BTW, When Would We Explicitly Use coroutineContext?

We might explicitly reference coroutineContext in cases like:

Accessing a Specific Dispatcher

Kotlin
println("Running on dispatcher: ${coroutineContext[CoroutineDispatcher]}")

Passing It to Another CoroutineScope

Kotlin
val newScope = CoroutineScope(coroutineContext + SupervisorJob())

Logging or Debugging Coroutine Context

Kotlin
println("Current coroutine context: $coroutineContext")

Basically, we don’t need to explicitly reference coroutineContext because it’s automatically used by coroutine builders (launch, async) inside the scope. However, if we need fine-grained control, debugging, or passing it to another scope, we can reference it explicitly.

Best Practices for Managing Custom CoroutineScopes

While defining a custom CoroutineScope can be useful, it should be done with caution. Here are some best practices:

Prefer Built-in Scopes When Possible

  • Use viewModelScope in Android ViewModels.
  • Use lifecycleScope for UI-related tasks.

Always Cancel the Scope

  • Call job.cancel() when the scope is no longer needed.
  • In Android, tie the scope’s lifecycle to an appropriate component.

Use Structured Concurrency

  • Instead of manually managing jobs, prefer SupervisorJob() where appropriate.
Kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

Avoid Launching Coroutines in GlobalScope

  • GlobalScope.launch is dangerous because it creates coroutines that run for the lifetime of the application.

Conclusion

CoroutineScope is essential for managing coroutines effectively. Creating a custom CoroutineScope can be useful when working outside lifecycle-aware components, but it requires careful handling to prevent memory leaks. By following best practices—such as canceling coroutines properly and preferring structured concurrency—you can ensure your coroutines are managed efficiently.

Kotlin Constructs

Unlocking Kotlin Constructs: The Secret to Cleaner & Smarter Code

Kotlin has revolutionized the way developers write Android and backend applications. It is known for its concise syntax, safety features, and expressive constructs. But what exactly are Kotlin Constructs, and how can they help you write cleaner and smarter code? Let’s dive in and unlock the secrets of Kotlin’s most powerful features.

What Are Kotlin Constructs?

Kotlin Constructs are the fundamental building blocks that make the language expressive and efficient. These include data classes, extension functions, smart casts, sealed classes, and more. They help developers reduce boilerplate code, improve readability, and enhance maintainability.

1. Data Classes — Less Code, More Power

One of Kotlin’s standout features is data classes. In Java, creating a simple model class with equals(), hashCode(), and toString() requires a lot of boilerplate. Kotlin simplifies this with a single line.

Kotlin
data class User(val name: String, val age: Int)

Why Use Data Classes?

  • Automatic toString(), equals(), and hashCode() Implementation
  • Copy Functionality: val newUser = user.copy(age = 25)
  • Destructuring Support: val (name, age) = user

This eliminates redundant code and makes the class cleaner and more readable.

2. Extension Functions — Adding Superpowers to Existing Classes

Ever wanted to add a function to an existing class without modifying its source code? Kotlin’s extension functions make this possible.

Kotlin
fun String.isEmailValid(): Boolean {
    return this.contains("@") && this.contains(".")
}

val email = "[email protected]"
println(email.isEmailValid()) // true

Why Use Extension Functions?

  • Improves Readability: Keeps code clean by eliminating utility classes.
  • Enhances Reusability: Works across multiple classes without modification.

3. Smart Casts — No More Manual Type Checking

In Java, checking types requires explicit casting, but Kotlin introduces smart casts that automatically infer types.

Kotlin
fun printLength(obj: Any) {
    if (obj is String) {
        println(obj.length) // Smart cast to String
    }
}

Why Use Smart Casts?

  • Avoids Explicit Casting: obj as String is not needed.
  • Enhances Safety: Reduces risk of ClassCastException.

4. Sealed Classes — The Better Alternative to Enums

Sealed classes restrict inheritance, making them ideal for representing fixed hierarchies like API responses or UI states.

Kotlin
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Data: ${result.data}")
        is Result.Error -> println("Error: ${result.message}")
    }
}

Why Use Sealed Classes?

  • Compile-Time Safety: Ensures all cases are handled in when expressions.
  • More Powerful than Enums: Supports different data types for each case.

5. The Power of apply, let, run, and also

Kotlin provides powerful scope functions to reduce redundant code and improve readability.

Kotlin
data class Person(var name: String, var age: Int)

val person = Person("Amol", 25).apply {
    age = 26 // Modifies within scope
}
println(person) // Person(name=Amol, age=26)

When to Use?

  • apply: Modifies an object and returns it.
  • let: Executes a function on an object and returns the result.
  • run: Similar to let, but executes in the object’s context.
  • also: Performs additional operations while keeping the original object.

Conclusion

Kotlin Constructs empower developers to write cleaner, smarter, and more efficient code. By leveraging data classes, extension functions, smart casts, sealed classes, and scope functions, you can significantly improve code maintainability and readability.

find the two smallest numbers

Kotlin Coding Challenge: Get the Two Smallest Numbers from Any List

When working with lists in Kotlin, a common task is to find the two smallest numbers. Whether you’re solving coding challenges, optimizing algorithms, or simply manipulating data, knowing how to efficiently extract the smallest elements is useful.

In this blog, we’ll explore different ways to find the two smallest numbers in a Kotlin list. We’ll keep it simple, efficient, and easy to understand. Let’s dive in..!

Understanding the Problem

Given a list of numbers, our goal is to identify the two smallest values and return them in ascending order. For example:

Kotlin
val numbers = listOf(5, 2, 9, 1, 7, 3)
// Expected output: 1, 2

Now, let’s explore different ways to achieve this in Kotlin.

1. Using sorted()

The simplest way to find the two smallest numbers in a Kotlin list is by sorting the list and picking the first two elements.

Kotlin
fun findTwoSmallest(numbers: List<Int>): Pair<Int, Int>? {
    if (numbers.size < 2) return null // Ensure the list has at least two elements
    val sortedNumbers = numbers.sorted()
    return Pair(sortedNumbers[0], sortedNumbers[1])
}

fun main() {
    val numbers = listOf(5, 2, 9, 1, 7, 3)
    val result = findTwoSmallest(numbers)
    println(result) // Output: (1, 2)
}

Here,

  • We first check if the list contains at least two elements to avoid errors.
  • The sorted() function arranges the numbers in ascending order.
  • We return the first two elements as a Pair.

Time Complexity: O(n log n) due to sorting.

2. Using a Single Pass (Efficient Approach)

Sorting works well but isn’t the most efficient way. We can improve performance by scanning the list just once.

Kotlin
fun findTwoSmallestEfficient(numbers: List<Int>): Pair<Int, Int>? {
    if (numbers.size < 2) return null
    
    var smallest = Int.MAX_VALUE
    var secondSmallest = Int.MAX_VALUE
    
    for (num in numbers) {
        if (num < smallest) {
            secondSmallest = smallest
            smallest = num
        } else if (num < secondSmallest && num != smallest) {
            secondSmallest = num
        }
    }
    
    return Pair(smallest, secondSmallest)
}

fun main() {
    val numbers = listOf(5, 2, 9, 1, 7, 2, 1, 3, 2, 3) 
    val result = findTwoSmallestEfficient(numbers)
    println(result) // Output: (1, 2)
}

We initialize two variables (smallest and secondSmallest) to the largest possible integer values.

We iterate through the list once:

  • If a number is smaller than smallest, it becomes the new smallest, and the previous smallest moves to secondSmallest.
  • Otherwise, If number is greater than smallest but still smaller than secondSmallest, update secondSmallest.
  • This approach avoids sorting, making it much faster.

Time Complexity: O(n), since we only scan the list once.

3. Using minOrNull() and minus()

Kotlin provides built-in functions to make this task even simpler.

Kotlin
fun findTwoSmallestFunctional(numbers: List<Int>): Pair<Int, Int>? {
    val firstMin = numbers.minOrNull() ?: return null
    val secondMin = numbers.filter { it != firstMin }.minOrNull() ?: return null
    return Pair(firstMin, secondMin)
}

fun main() {
    val numbers = listOf(5, 2, 9, 1, 7, 3)
    val result = findTwoSmallestFunctional(numbers)
    println(result) // Output: (1, 2)
}

Here,

  • We find the smallest number using minOrNull().
  • We then filter out the smallest number and find the next smallest using minOrNull() again.
  • This method is concise but slightly less efficient than the single-pass method.

Time Complexity: O(n) for filtering + O(n) for finding the min → Overall, O(n).

Which Method Should You Use?

MethodTime ComplexityBest Use Case
Sorting (sorted())O(n log n)When readability is more important than speed
Single-pass (Efficient)O(n)When performance is a priority
Functional (minOrNull())O(n)When writing concise and idiomatic Kotlin

Conclusion

In this blog, we explored multiple ways to find the two smallest numbers in a Kotlin list. We covered sorting, a highly efficient single-pass approach, and a functional-style solution. Each method has its trade-offs in terms of readability and performance.

Kotlin Covariance

Kotlin Covariance Explained: How Preserved Subtyping Works

Kotlin is a powerful and modern programming language that makes working with generics easier than Java. One of the most important concepts when dealing with generics in Kotlin is covariance. If you’ve ever seen the out keyword in Kotlin and wondered what it does, this blog is for you. Let’s break down Kotlin covariance in a simple, easy-to-understand way.

What is Kotlin Covariance?

Covariance refers to preserving the subtyping relation between generic classes. In Kotlin, you can declare a class to be covariant on a specific type parameter by using the out keyword before the type parameter’s name.

A covariant class is a generic class (we’ll use Producer as an example) for which the following holds: Producer<A> is a subtype of Producer<B> if A is a subtype of B. We say that the subtyping is preserved. For example, Producer<Cat> is a subtype of Producer<Animal> because Cat is a subtype of Animal.

Here’s an example of the Producer interface using the out keyword:

Kotlin
interface Producer<out T> {
    fun produce(): T
}

Flexible Function Argument and Return Value Passing

Covariance in Kotlin allows you to pass values of a class as function arguments and return values, even when the type arguments don’t exactly match the function definition. This means that you can use a more specific type as a substitute for a more generic type.

Suppose we have a hierarchy of classes involving Animal, where Cat is a subclass of Animal. We also have a generic interface called Producer, which represents a producer of objects of type T. We’ll make the Producer interface covariant by using the out keyword on the type parameter.

Kotlin
interface Producer<out T> {
    fun produce(): T
}

Now, let’s define a class AnimalProducer that implements the Producer interface for the Animal type:

Kotlin
class AnimalProducer : Producer<Animal> {
    override fun produce(): Animal {
        return Animal()
    }
}

Since Animal is a subtype of Animal, we can use AnimalProducer wherever a Producer<Animal> is expected.

Now, let’s define another class CatProducer that implements the Producer interface for the Cat type:

Kotlin
class CatProducer : Producer<Cat> {
    override fun produce(): Cat {
        return Cat()
    }
}

Since Cat is a subtype of Animal, we can also use CatProducer wherever a Producer<Animal> is expected. This is possible because we declared the Producer interface as covariant.

Now, let’s see how covariance allows us to pass these producers as function arguments and return values:

Kotlin
fun feedAnimal(producer: Producer<Animal>) {
    val animal = producer.produce()
    animal.feed()
}

fun main() {
    val animalProducer = AnimalProducer()
    val catProducer = CatProducer()
    feedAnimal(animalProducer) // Passes an AnimalProducer, which is a Producer<Animal>
    feedAnimal(catProducer) // Passes a CatProducer, which is also a Producer<Animal>
}

In the feedAnimal function, we expect a Producer<Animal> as an argument. With covariance, we can pass both AnimalProducer and CatProducer instances because Producer<Cat> is a subtype of Producer<Animal> due to the covariance declaration.

This demonstrates how covariance allows you to treat more specific types (Producer<Cat>) as if they were more generic types (Producer<Animal>) when it comes to function arguments and return values.

BTW, How covariance guarantees type safety?

Suppose we have a class hierarchy involving Animal, where Cat is a subclass of Animal. We also have a Herd class that represents a group of animals.

Kotlin
open class Animal {
    fun feed() { /* feeding logic */ }
}

class Herd<T : Animal> {            // The type parameter isn't declared as covariant
    val size: Int get() = /* calculate the size of the herd */
    operator fun get(i: Int): T { /* get the animal at index i */ }
}

fun feedAll(animals: Herd<Animal>) {
    for (i in 0 until animals.size) {
        animals[i].feed()
    }
}

Now, suppose you have a function called takeCareOfCats, which takes a Herd<Cat> as a parameter and performs some operations specific to cats.

Kotlin
class Cat : Animal() {
    fun cleanLitter() { /* clean litter logic */ }
}

fun takeCareOfCats(cats: Herd<Cat>) {
    for (i in 0 until cats.size) {
        cats[i].cleanLitter()
        // feedAll(cats) // This line would cause a type-mismatch error, Error: inferred type is Herd<Cat>, but Herd<Animal> was expected
    }
}

In this case, if you try to pass the cats herd to the feedAll function, you’ll get a type-mismatch error during compilation. This happens because you didn’t use any variance modifier on the type parameter T in the Herd class, making the Herd<Cat> incompatible with Herd<Animal>. Although you could use an explicit cast to overcome this issue, it is not a recommended approach.

To make it work correctly, you can make the Herd class covariant by using the out keyword on the type parameter:

Kotlin
class Herd<out T : Animal> {   // The T parameter is now covariant.
    // ...
}

fun takeCareOfCats(cats: Herd<Cat>) {
    for (i in 0 until cats.size) {
        cats[i].cleanLitter()
    }
    feedAll(cats) // Now this line works because of covariance, You don't need a cast.
}

By marking the type parameter as covariant, you ensure that the subtyping relation is preserved, and T can only be used in “out” positions. This guarantees type safety and allows you to pass a Herd<Cat> where a Herd<Animal> is expected.

Usage of covariance

Covariance in Kotlin allows you to make a class covariant on a type parameter, but it also imposes certain constraints to ensure type safety. The type parameter can only be used in “out” positions, which means it can produce values of that type but not consume them.

You can’t make any class covariant: it would be unsafe. Making the class covariant on a certain type parameter constrains the possible uses of this type parameter in the class. To guarantee type safety, it can be used only in so-called out positions, meaning the class can produce values of type T but not consume them. Uses of a type parameter in declarations of class members can be divided into “in” and “out” positions.

Let’s consider a class that declares a type parameter T and contains a function that uses T. We say that if T is used as the return type of a function, it’s in the out position. In this case, the function produces values of type T. If T is used as the type of a function parameter, it’s in the in position. Such a function consumes values of type T.

The function parameter type is called in position, and the function return type is called out position

The out keyword on a type parameter of the class requires that all methods using T have T only in “out” positions and not in “in” positions. This keyword constrains possible use of T, which guarantees safety of the corresponding subtype relation.

Let’s understand this with some examples. Consider the Herd class, which is declared as Herd<out T : Animal>. The type parameter T is used only in the return value of the get method. This is an “out” position, and it is safe to declare the class as covariant. For instance, Herd<Cat> is considered a subtype of Herd<Animal> because Cat is a subtype of Animal.

Kotlin
class Herd<out T : Animal> {
    val size: Int = ...
    operator fun get(i: Int): T { ... }   // Uses T as the return type
}

Similarly, the List<T> interface in Kotlin is covariant because it only defines a get method that returns an element of type T. Since there are no methods that store values of type T, it is safe to declare the class as covariant.

Kotlin
interface List<out T> : Collection<T> {
    operator fun get(index: Int): T          // Read-only interface that defines only methods that return T (so T is in the “out” position)
    // ...
}

You can also use the type parameter T as a type argument in another type. For example, the subList method in the List interface returns a List<T>, and T is used in the “out” position.

Kotlin
interface List<out T> : Collection<T> {
    fun subList(fromIndex: Int, toIndex: Int): List<T>    // Here T is in the “out” position as well.
    // ...
}

However, you cannot declare MutableList<T> as covariant on its type parameter because it contains methods that both consume and produce values of type T. Therefore, T appears in both “in” and “out” positions, and making it covariant would be unsafe.

Kotlin
interface MutableList<T> : List<T>, MutableCollection<T> {    //MutableList can’t be declared as covariant on T …
    override fun add(element: T): Boolean   // … because T is used in the “in” position.
}

The compiler enforces this restriction. It would report an error if the class was declared as covariant: Type parameter T is declared as ‘out’ but occurs in ‘in’ position.

Why and When to Use Kotlin Covariance?

Covariance helps to preserve subtyping relationships. Without covariance, the Kotlin compiler would not allow a subtype to be assigned to a supertype in a generic context.

Use out when:

  • Your generic type is a producer of values.
  • You only need to return values of the generic type.
  • You want to enable subtype assignments safely.

Conclusion

Covariance in Kotlin simplifies working with generics while maintaining type safety. By using the out keyword, you can allow subtype assignments in a controlled manner. Understanding Kotlin Covariance helps you write more flexible and safe generic code, making your Kotlin programs more robust and maintainable.

Start using covariance in your Kotlin projects today and experience the power of type-safe, flexible generics..!

Interfaces in Kotlin

How to Use Interfaces in Kotlin Like a Pro

Interfaces in Kotlin are a powerful tool that allows developers to achieve abstraction and multiple inheritance in an elegant way. If you’re looking to master Interfaces in Kotlin, this guide will take you through everything you need to know in a simple and practical way. By the end, you’ll be using interfaces like a pro!

Interfaces in Kotlin are a powerful tool that allows developers to achieve abstraction and multiple inheritance in an elegant way. If you’re looking to master Interfaces in Kotlin, this guide will take you through everything you need to know in a simple and practical way. By the end, you’ll be using interfaces like a pro!

What is an Interface in Kotlin?

An interface in Kotlin is a blueprint of a class that defines a set of functions and properties without implementing them. A class that implements an interface must provide the implementation of its functions unless they have default implementations.

Unlike abstract classes, interfaces in Kotlin allow multiple inheritance, making them an essential feature for building scalable and maintainable applications.

Declaring an Interface

Creating an interface in Kotlin is straightforward. You use the interface keyword followed by the name of the interface.

Kotlin
interface Animal {
    fun makeSound()
}

Here, Animal is an interface that declares a function makeSound(). Any class implementing this interface must provide its own implementation of makeSound().

Implementing an Interface

A class implements an interface using the : symbol, just like inheriting a class.

Kotlin
class Dog : Animal {
    override fun makeSound() {
        println("Bark!")
    }
}

Here,

  • The Dog class implements the Animal interface.
  • It overrides the makeSound() function and provides a specific implementation.
  • If a class implements an interface but does not provide an implementation for a function that lacks a default implementation, the code will not compile, resulting in an error.

Default Implementations in Interfaces

Kotlin interfaces can have default implementations for functions, reducing code duplication.

Kotlin
interface Vehicle {
    fun start() {
        println("Vehicle is starting...")
    }
}

class Car : Vehicle {
    // No need to override start() unless customization is needed
}
fun main() {
    val myCar = Car()
    myCar.start() // Output: Vehicle is starting...
}

Why Use Default Implementations?

  • To avoid redundant code.
  • It allow future updates without breaking existing classes.
  • Provides a base behavior that classes can override if needed.

Interfaces with Properties

Unlike Java, interfaces in Kotlin can have properties. However, they cannot store state — only define properties that must be implemented by the implementing class.

Kotlin
interface Person {
    val name: String // Abstract property
    fun introduce() {
        println("Hi, I'm $name")
    }
}

class Student(override val name: String) : Person

fun main() {
    val student = Student("Amol")
    student.introduce() // Output: Hi, I'm Amol
}

Please Note:

  • Interface properties don’t have a backing field.
  • Implementing classes must override properties and provide values.
  • Functions can use properties within the interface.

Multiple Interfaces in Kotlin

A major advantage of interfaces in Kotlin is that a class can implement multiple interfaces, enabling multiple inheritance.

Kotlin
interface Printable {
    fun printData() {
        println("Printing data...")
    }
}

interface Scannable {
    fun scanData() {
        println("Scanning data...")
    }
}

class MultiFunctionPrinter : Printable, Scannable

fun main() {
    val device = MultiFunctionPrinter()
    device.printData()  // Output: Printing data...
    device.scanData()    // Output: Scanning data...
}

How This Helps:

  • Allows a class to inherit behavior from multiple sources.
  • Enables modular and reusable code.
  • Prevents unnecessary subclassing.

But what if the function signatures are the same? Then what?

Handling Conflicts When Implementing Multiple Interfaces

If multiple interfaces provide the same function signature, Kotlin requires you to resolve the conflict by explicitly specifying which implementation to use.

Kotlin
interface A {
    fun show() {
        println("From A")
    }
}

interface B {
    fun show() {
        println("From B")
    }
}

class C : A, B {
    override fun show() {
        super<A>.show() // Specify which implementation to use
    }
}

fun main() {
    val obj = C()
    obj.show() // Output: From A
}

Conflict Resolution:

  • If two interfaces have the same function, Kotlin forces you to explicitly choose one.
  • The super<InterfaceName> syntax helps resolve ambiguity.
  • You must explicitly choose which implementation to call.
  • It helps avoid ambiguity and ensures predictable behavior.

Using Interfaces for Dependency Injection

A common design pattern in Kotlin development is Dependency Injection (DI) using interfaces.

Kotlin
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) {
        println("Log: $message")
    }
}

class UserService(private val logger: Logger) {
    fun registerUser(name: String) {
        logger.log("User $name registered successfully")
    }
}

fun main() {
    val logger = ConsoleLogger()
    val userService = UserService(logger)
    userService.registerUser("Amol") // Output: Log: User Amol registered successfully
}

Why This Works Well:

  • Decouples dependencies, making testing and modifications easier.
  • Enables mocking in unit tests by substituting different implementations.
  • Supports scalability, as different logging mechanisms (file, database) can be plugged in easily.

When to Use Interfaces in Kotlin

Using interfaces in Kotlin makes sense in scenarios where:

  • You need multiple inheritance (since Kotlin doesn’t support multiple class inheritance).
  • You want to enforce a contract that multiple classes must follow.
  • You need default implementations to share common logic without creating base classes.
  • You want to design loosely coupled and modular components.

Conclusion

Interfaces in Kotlin provide a simple yet powerful way to achieve abstraction, multiple inheritance, and code reuse. By learning how to declare and implement interfaces, use default methods, and manage multiple interfaces, you can create more structured and maintainable Kotlin applications.

Now that you have a solid understanding of interfaces in Kotlin, try incorporating them into your projects. You’ll see how they make your code more flexible and modular!

Capturing Objects in Kotlin Lambdas

Capturing Objects in Kotlin Lambdas: Why External Modifications Reflect Inside

Kotlin lambdas are powerful and flexible, but they have a behavior that often surprises developers: when a lambda captures an object, external modifications to that object reflect inside the lambda. This can lead to unexpected side effects, especially if you’re dealing with mutable data.

In this blog, we’ll explore why this happens, how capturing works in Kotlin lambdas, and how to handle this behavior effectively.

What Does “Capturing Objects in Kotlin Lambdas” Mean?

When we pass a variable to a lambda in Kotlin, we might assume that the lambda gets a copy of it. However, that’s not entirely true. Instead, Kotlin captures references to objects, not their values.

This means that if the captured object is mutable and changes externally, the lambda will see the updated state.

Let’s look at a simple example to understand this behavior:

Kotlin
fun main() {
    val numbers = mutableListOf(1, 2, 3)
    val lambda = { println("Inside lambda: $numbers") }
    
    numbers.add(4)
    lambda()  // The lambda sees the updated list
}


//OUTPUT

Inside lambda: [1, 2, 3, 4]

Here’s what happens:

  • The lambda captures a reference to numbers, not a snapshot or copy of its values.
  • When we modify numbers outside the lambda, the lambda reflects those changes.
  • When we invoke lambda(), it prints the updated list with 4 included.

Capturing References vs. Values

It is important to understand when Kotlin captures a reference and when it captures a value.

Kotlin
fun main() {
    val list = mutableListOf(1, 2, 3)
    var number = 10

    val lambda = {
        println("Captured list: $list")
        println("Captured number: $number")
    }

    list.add(4)
    number = 20
    
    lambda()
}


// OUTPUT

Captured list: [1, 2, 3, 4]  // Captured reference, reflects changes
Captured number: 20          // Captured reference, reflects changes

Why This Happens?

  • Mutable Objects (list) → Captured by reference → Any external changes are visible inside the lambda.
  • Variables (var number) → Captured by reference, not value → External changes are reflected inside the lambda.
  • If Immutable values (val number = 10) → Captured by value, meaning they remain unchanged.

Learn more at: [Main Article URL]

Conclusion

Capturing objects in Kotlin lambdas is a powerful feature, but it requires careful handling. When a lambda captures a mutable variable, it retains a reference to it, meaning external modifications will be reflected inside the lambda and vice versa.

To avoid unintended side effects:

  • Use immutable data structures where possible to prevent modifications.
  • Explicitly create a copy (list.toList(), copy() for data classes) if you need an independent object.
  • Prefer passing objects as function parameters instead of capturing them.

By mastering these techniques, you can leverage Kotlin lambdas effectively while ensuring code clarity and correctness.

error: Content is protected !!