Amol Pawar

git bisect

How Git Bisect Pinpoints the Problem: Debugging Like a Pro

Debugging can be frustrating, especially when a bug appears in a large codebase and you have no idea when it was introduced. Instead of manually sifting through commits, Git Bisect can help you track down the exact commit that introduced the bug efficiently. In this blog, we’ll explore how Git Bisect works, walk through a practical example, and provide useful tips to make debugging smoother.

What is Git Bisect?

It is a built-in Git tool that helps you perform a binary search through your commit history to find the exact commit that introduced a bug. Instead of checking each commit one by one, it narrows down the search range quickly by repeatedly dividing the history in half.

This makes Git Bisect extremely powerful, especially in large repositories where manually checking each commit would be time-consuming.

How Does It Work?

The process is simple:

  1. You start a bisect session.
  2. Mark a known good commit where the bug is absent.
  3. Mark a bad commit where the bug is present.
  4. Git automatically checks out a commit between those two.
  5. You test the code at that commit and mark it as either “good” or “bad.”
  6. Git continues this process, narrowing the search until it finds the exact problematic commit.

This approach significantly reduces the number of commits you need to check manually.

Step-by-Step Guide to Using It

Imagine you’re working on a project and notice a feature is broken, but you’re not sure when the bug was introduced.

1. Start the Git Bisect Session

Bash
$ git bisect start

This command initiates a Git Bisect session, putting Git in bisect mode.

2. Mark a “Good” and “Bad” Commit

You need to tell Git where the bug exists and where it doesn’t.

  • Identify a commit where the bug exists and mark it as bad:
Bash
$ git bisect bad
  • Find a commit where the bug didn’t exist and mark it as good:
Bash
$ git bisect good <commit-hash>

Git will now start checking commits between these two points.

3. Git Picks a Commit for You to Test

Git automatically checks out a commit in the middle of the range. Now, test your code to see if the bug is present.

  • If the bug is present, mark it as bad:
Bash
$ git bisect bad
  • If the bug is not present, mark it as good:
Bash
$ git bisect good

Each time you mark a commit as good or bad, Git Bisect picks another commit in between to test.

4. Repeat Until the Problematic Commit is Found

Git will continue selecting commits until it pinpoints the exact commit that introduced the bug. Once identified, Git will display the commit hash and message of the problematic commit.

Bash
// something like this

<commit-hash> is the first bad commit  

5. End the Bisect Session

Once you’ve found the bad commit, exit bisect mode:

Bash
$ git bisect reset

This restores your repository to its original state before the bisect process.

Automating with a Script

If testing the bug manually is tedious, you can automate the process using a script. Here’s how:

$ git bisect start
$ git bisect bad
$ git bisect good <commit-hash>
$ git bisect run ./test-script.sh

Replace ./test-script.sh with your actual test script that returns 0 for good commits and 1 for bad commits. Git Bisect will run the script automatically and stop when the bad commit is found.

Best Practices

  • Choose accurate good/bad commits: Ensure your “good” commit is actually bug-free to avoid misleading results.
  • Use automation when possible: Automating the testing process speeds up bisecting significantly.
  • Document findings: Keep notes on what you find to avoid redoing the process later.
  • Reset after bisecting: Always run git bisect reset to return to the original branch.

Why Use Git Bisect?

  • Saves time: Finds the problematic commit quickly using a binary search.
  • Works with any Git project: No additional tools or setup required.
  • Automatable: Can integrate with test scripts to remove manual testing effort.
  • Accurate: Pinpoints the exact commit that introduced a bug, reducing debugging guesswork.

Conclusion

Git Bisect is an essential tool for any developer who wants to debug efficiently. By leveraging binary search, it drastically cuts down the time spent hunting for a bug in large codebases. Whether you’re using it manually or automating the process, it’s a powerful addition to your Git workflow.

Next time you’re stuck debugging a mysterious issue, give Git Bisect a try — it’ll make you look like a pro..!

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...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
android studio meerkat

Android Studio Meerkat Is Here: 10 Game-Changing Features You Must Know

Android Studio Meerkat is officially here — and it’s more than just a cute name. This release introduces powerful updates that aim to speed up development, enhance debugging, and improve the overall Jetpack Compose experience. Whether you’re building complex apps or just starting out, Meerkat brings features that can seriously boost your productivity.

Let’s explore the 10 most game-changing features you absolutely must know.

1. Live Edit for Jetpack Compose Is Now Stable

Live Edit is no longer experimental! You can now see real-time changes in Compose previews or even on a device/emulator without restarting the app. This drastically speeds up your UI development workflow.

No more rebuilding. Just tweak and watch it update live.

2. IntelliJ Platform Upgrade (2023.3)

Meerkat includes the IntelliJ IDE platform version 2023.3, bringing in:

  • Smarter code completion
  • Better navigation tools
  • Performance enhancements

This makes the entire experience smoother and more intelligent, especially with Kotlin and Gradle support.

3. Device Mirroring (Now on Stable)

You can now mirror a physical device’s screen directly inside Android Studio — wirelessly or via USB. Use it to:

  • Interact with your app in real time
  • Drag and drop files
  • Take screenshots and screen recordings

Super useful for quick testing and demos.

4. Improved Compose State Inspection

The Compose State Inspector is now more powerful, letting you:

  • View recompositions
  • Understand state relationships
  • Debug UI performance issues with clarity

No more guessing which state triggered a recomposition!

5. Updated Profiler UI

Performance profiling got a visual refresh with:

  • Cleaner timeline views
  • Easier navigation between CPU, memory, and network usage
  • Better flame graphs

Perfect for finding those sneaky bottlenecks.

6. Version Catalog Support in New Project Wizard

Using libs.versions.toml? You’re in luck. Meerkat’s New Project Wizard now supports Version Catalogs out of the box. This helps:

  • Keep dependencies consistent
  • Share versions across modules
  • Simplify upgrades

7. Updated Layout Inspector for Compose

Compose developers can now enjoy:

  • Enhanced real-time tree views
  • Better performance when inspecting deep hierarchies
  • A cleaner UI for navigating views and modifiers

This turns debugging complex UIs into a visual breeze.

8. AI-Powered Code Suggestions (Experimental)

Meerkat brings in early-stage AI code assistance features — like smart suggestions, code completions, and intent-aware quick fixes.

Experimental, but promising! Great for productivity bursts or learning APIs.

9. Gradle Sync & Build Speed Improvements

Google has optimized how Meerkat interacts with Gradle:

  • Faster sync times
  • Reduced memory usage
  • Improved project indexing

The result? Less waiting, more coding.

10. Enhanced Privacy Sandbox Tools

If you’re developing apps that integrate with the Privacy Sandbox on Android, Meerkat introduces new SDK Runtime tools to:

  • Inspect runtime behavior
  • Debug SDK communications
  • Ensure privacy-first design patterns

Conclusion

Android Studio Meerkat isn’t just a minor update — it’s a leap forward in usability, performance, and Jetpack Compose support. From Live Edit going stable to device mirroring and AI-assisted coding, this release is packed with value.

So if you haven’t upgraded yet, now is the time to install Meerkat and explore these productivity-boosting features.

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,...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
How to Build a Weather App in Kotlin

How to Build a Weather App in Kotlin Using MVVM, Retrofit, and Room

Building an Android app that fetches live weather data is a great way to get hands-on with networking, local storage, and clean architecture principles. In this guide, we’ll build a Weather App in Kotlin using MVVM, Retrofit for API calls, and Room for local caching. It’s a solid project for interviews or real-world use — especially if...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Declaration-Site Variance

Understanding Kotlin Declaration-Site Variance: Kotlin’s In & Out vs. Java Wildcards

Kotlin provides a powerful type system that includes declaration-site variance, making generics more flexible and safe. If you’ve ever worked with generics in Java, you might recall the ? extends and ? super keywords. Kotlin simplifies this with in and out modifiers. In this blog, we’ll break down declaration-site variance in Kotlin, explore how in and out...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Restrictions of Reified Type Parameters

Top 4 Restrictions of Reified Type Parameters in Kotlin and How to Work Around Them

Kotlin is a powerful language that brings many improvements over Java, especially when it comes to generics. One of its unique features is reified type parameters, which solve some of the limitations of Java’s generics. However, there are certain restrictions on reified type parameters that developers should be aware of. In this post, we’ll dive deep into these restrictions, understand why they exist, and explore how to work around them.

What Are Reified Type Parameters?

In Kotlin, generic type parameters are usually erased at runtime due to type erasure, just like in Java. However, by using the inline keyword along with the reified modifier, you can retain type information at runtime.

Without Reified Type Parameters

Kotlin
fun <T> getClassType(): Class<T> {
    return T::class.java // Error: Cannot use 'T' as a reified type parameter
}

The above code will not work because T is erased at runtime, making it impossible to retrieve its class type.

With Reified Type Parameters

Kotlin
inline fun <reified T> getClassType(): Class<T> {
    return T::class.java
}

fun main() {
    println(getClassType<String>()) // Output: class java.lang.String
}

By marking T as reified, we can access its type at runtime, allowing operations like T::class.java without issues.

Key Restrictions on Reified Type Parameters

While reified type parameters are incredibly useful, they come with some limitations. Let’s explore these restrictions in detail.

1. Reified Type Parameters Can Only Be Used in Inline Functions

One of the biggest restrictions on reified type parameters is that they are only allowed inside inline functions. If you try to use them in regular functions, you’ll get a compilation error.

Kotlin
fun <reified T> someFunction() { // Error: Type parameter T cannot be reified
    println(T::class.java)
}

Why Does This Restriction Exist?

Kotlin’s reified type parameters work by inlining the function, meaning the actual type is substituted at the call site. Without inlining, the type information would still be erased, making it impossible to retain the generic type at runtime.

Workaround: To use reified types, always mark your function as inline:

Kotlin
inline fun <reified T> someFunction() {
    println(T::class.java)
}

2. Reified Types Cannot Be Used in Class-Level Generics

Reified type parameters are tied to functions, not classes. If you try to use a reified type in a class definition, Kotlin will complain:

Kotlin
class MyClass<reified T> {  // This is not allowed!
    fun printType() {
        println("The type is: ${T::class.simpleName}")
    }
}

Error:

Kotlin
Reified type parameters are only allowed for inline functions.

Workaround: Instead of using a class-level generic with reified, define an inline function inside the class:

Kotlin
class MyClass<T> {
    inline fun <reified T> printType() {
        println("The type is: ${T::class.simpleName}")
    }
}

3. Reified Type Parameters Cannot Be Used in Class-Level Properties

Another important restriction on reified type parameters is that they cannot be used as class properties.

Kotlin
class Example<T> {
    val type = T::class // Error: Cannot use 'T' as a reified type parameter
}

Since reified type parameter require inline functions to retain type information, they cannot be stored in class properties. The class itself does not have access to the type at runtime due to type erasure.

Workaround: If you need to store a type reference, you can pass the KClass instance explicitly:

Kotlin
class Example<T>(private val clazz: Class<T>) {
    fun getType(): Class<T> = clazz
}

fun main() {
    val example = Example(String::class.java)
    println(example.getType()) // Output: class java.lang.String
}

4. Cannot Create New Instances of Reified Types

Unlike Java’s reflection-based approach (T::class.java.newInstance()), Kotlin prevents the direct instantiation of reified types:

Kotlin
inline fun <reified T> createInstance(): T {
    return T()  // Compilation error!
}

Error:

Kotlin
Cannot create an instance of T because it has no zero-argument constructor.

Workaround: Pass a factory function or a class reference:

Kotlin
inline fun <reified T> createInstance(factory: () -> T): T {
    return factory()
}

Conclusion

Reified type parameters are a powerful feature in Kotlin, allowing us to retain type information at runtime. However, there are several restrictions on reified type parameters to be mindful of:

  • Reified types only work in inline functions.
  • They cannot be used in class-level generics or properties.
  • You cannot instantiate a reified type directly.

By keeping these points in mind, you can write more efficient and type-safe Kotlin code while leveraging the full power of reified type parameters.

Object in Kotlin

Understanding Object in Kotlin: A Deep Dive

Kotlin is a powerful and expressive programming language that brings many modern features to Android and backend development. One of its standout features (object in kotlin) is the object keyword, which provides a clean and concise way to define singleton objects, companion objects, and anonymous objects. If you’re coming from Java, understanding how Kotlin handles objects can significantly improve your code’s readability and efficiency.

In this guide, we’ll explore Kotlin’s object keyword in depth, covering its different use cases with examples.

What is an Object in Kotlin?

In Kotlin, an object is an instance of a class that is created automatically and can be referenced directly. Unlike traditional class instantiation, where you need to explicitly create an instance using new, Kotlin’s object keyword allows you to create singletons and anonymous objects efficiently.

Objects in Kotlin can be categorized into:

  • Singleton Objects
  • Companion Objects
  • Anonymous Objects

Each of these serves a different purpose. Let’s break them down one by one.

Singleton Object in Kotlin

A singleton is a class that has only one instance throughout the program. Instead of creating multiple instances, you define a singleton using object.

Kotlin
object Database {
    val name = "MyDatabase"
    
    fun connect() {
        println("Connected to $name")
    }
}

fun main() {
    Database.connect() // Output: Connected to MyDatabase
}
  • No need for manual instantiation — The Database object is automatically created.
  • Thread-safe — Since there is only one instance, it avoids issues with multiple instances.
  • Useful for managing shared resources, such as database connections or network clients.

Companion Objects: Static-like Behavior in Kotlin

Unlike Java, Kotlin does not have static methods. Instead, you can use companion objects inside classes to define functions and properties that belong to the class itself rather than its instances.

Kotlin
class User(val name: String) {
    companion object {
        fun createGuest() = User("Guest")
    }
}

fun main() {
    val guest = User.createGuest()
    println(guest.name) // Output: Guest
}

Why Use Companion Objects?

  • Acts like a static method in Java — You don’t need to create an instance of User to call createGuest().
  • Encapsulates related functionality — Keeps utility functions inside the class instead of defining them separately.
  • Can implement interfaces — Unlike static methods in Java, a companion object can extend interfaces.

Anonymous Objects: One-Time Use Classes 

Sometimes, you need an object only once, such as for event listeners or implementing an interface on the fly. Kotlin provides anonymous objects (Object Expression) for this purpose.

Kotlin
interface ClickListener {
    fun onClick()
}

fun main() {
    val buttonClickListener = object : ClickListener {
        override fun onClick() {
            println("Button clicked!")
        }
    }
    buttonClickListener.onClick() // Output: Button clicked!
}

Key Benefits:

  • No need to create a separate class — Saves boilerplate code.
  • Good for event listeners and callbacks — Common in Android development.
  • Short-lived instances — Automatically garbage collected when no longer needed.

Object Expression vs. Object Declaration

There are two primary ways to use objects in Kotlin: Object Expressions and Object Declarations. Let’s compare them:

FeatureObject ExpressionObject Declaration
InstantiationEvery time usedOnly once (Singleton)
Named?NoYes
Use caseOne-time useGlobal state, shared instance

When to Use Objects in Kotlin?

  • Use a singleton object when you need a single, shared instance (e.g., logging, database connections).
  • Use a companion object when you need static-like methods and properties within a class.
  • Use an anonymous object when you need a temporary implementation of an interface or class (e.g., callbacks, event listeners).

Conclusion

Kotlin’s object keyword is a powerful tool that simplifies singleton patterns, enables static-like functionality, and allows for concise anonymous implementations. By understanding singleton objects, companion objects, and anonymous objects, you can write cleaner, more efficient, and idiomatic Kotlin code.

Kotlin Variance

Kotlin Variance Demystified: Understanding Generics & Subtyping

Kotlin is a powerful, modern programming language with robust support for generics. But when dealing with generics, you often run into the concept of variance, which can be tricky to grasp at first. In this post, we’ll break down Kotlin variance in a simple and engaging way, ensuring you fully understand generics and subtyping.

What is Kotlin Variance?

Variance defines how generic types relate to each other in the context of subtyping. Consider this:

Kotlin
open class Animal
class Dog : Animal()

In normal inheritance, Dog is a subtype of Animal. But does List<Dog> automatically become a subtype of List<Animal>? The answer is no, unless we explicitly declare variance.

Kotlin provides three ways to handle variance:

  • Covariance (out)
  • Contravariance (in)
  • Invariance (default behavior)

Let’s explore each of these concepts with examples.

Covariance (out) – Producer

Covariant types allow a generic type to be a subtype of another generic type when its type parameter is only used as an output (producer). It is declared using the out keyword.

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

This means that Producer<Dog> can be used where Producer<Animal> is expected:

Kotlin
class DogProducer : Producer<Dog> {
    override fun produce(): Dog = Dog()
}

val producer: Producer<Animal> = DogProducer() // Works fine

Why Use out?

  • It ensures type safety.
  • Used when a class only returns values of type T.
  • Common in collections like List<T>, which can only produce items, not modify them.

Example:

Kotlin
fun feedAnimals(producer: Producer<Animal>) {
    val animal: Animal = producer.produce()
    println("Feeding ${animal::class.simpleName}") //O/P - Feeding Dog
}

Since Producer<Dog> is a subtype of Producer<Animal>, this works without any issues.

Contravariance (in) – Consumer

Contravariant types allow a generic type to be a supertype of another generic type when its type parameter is only used as an input (consumer). It is declared using the in keyword.

Kotlin
interface Consumer<in T> {
    fun consume(item: T)
}

This means that Consumer<Animal> can be used where Consumer<Dog> is expected:

Kotlin
class AnimalConsumer : Consumer<Animal> {
    override fun consume(item: Animal) {
        println("Consuming ${item::class.simpleName}")
    }
}

val consumer: Consumer<Dog> = AnimalConsumer() // Works fine

Why Use in?

  • Again it ensures type safety.
  • Used when a class only takes in values of type T.
  • Common in function parameters, like Comparator<T>.

Example:

Kotlin
fun addDogsToShelter(consumer: Consumer<Dog>) {
    consumer.consume(Dog())
}

Since Consumer<Animal> is a supertype of Consumer<Dog>, this works perfectly.

Invariance (Default Behavior: No in or out)

By default, generics in Kotlin are invariant, meaning Box<Dog> is NOT a subtype or supertype of Box<Animal>, even though Dog is a subtype of Animal. Means, they do not support substitution for subtypes or supertypes.

Kotlin
class Box<T>(val item: T)

val dogBox: Box<Dog> = Box(Dog())
val animalBox: Box<Animal> = dogBox // Error: Type mismatch

Why?

Without explicit variance, Kotlin prevents unsafe assignments. If variance is not declared, Kotlin assumes that T can be both produced and consumed, making it unsafe to assume subtyping.

Star Projection (*) – When Type is Unknown

Sometimes, you don’t know the exact type parameter but still need to work with a generic class. Kotlin provides star projection (*) to handle such cases.

Example:

Kotlin
fun printList(list: List<*>) {
    for (item in list) {
        println(item) // Treated as Any?
    }
}

A List<*> means it could be List<Any>, List<String>, List<Int>, etc., but we cannot modify it because we don’t know the exact type.

Best Practices for Kotlin Variance

  • Use out when the type is only produced (e.g., List<T>).
  • Use in when the type is only consumed (e.g., Comparator<T>).
  • Keep generics invariant unless variance is required.
  • Use star projections (*) when you don’t know the type but need read access.

Conclusion

Understanding Kotlin Variance is crucial for writing flexible and type-safe code. Covariance (out) is used for producers, contravariance (in) for consumers, and invariance when both roles exist. By mastering Kotlin Variance concepts, you can work effectively with generics and subtyping in Kotlin.

Kotlin Coroutines

Kotlin Coroutines in Android: The Ultimate Guide to Asynchronous Programming

If you’ve ever dealt with asynchronous programming in Android, you know it can get messy fast. Callback hell, thread management, and performance issues make it a nightmare. That’s where Kotlin Coroutines come in. Coroutines provide a simple, structured way to handle background tasks without blocking the main thread, making your code more readable and efficient....

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
error: Content is protected !!