r/Kotlin

Built an Android interview prep site
▲ 49 r/Kotlin+1 crossposts

Built an Android interview prep site

Hey ppl,

Used my Saturday to build this little project:

Website - https://androiddevkit.com/

Github - https://github.com/vishnusreddy/androiddevkit

Whenever I was preparing for Android interviews, I didn't have one specific place to learn from. Kotlin questions were in one place, coroutines and Flow in another, Compose stuff somewhere else, and actual Android interview experiences were usually buried deep in Reddit threads or random blog posts.

Leetcode, geeksforgeeks and all these sites barely seem to concentrate on Android devs.

So I tried building one, specifically for Android devs.

Right now it covers:

  • Kotlin
  • Coroutines and Flows
  • Android fundamentals
  • Jetpack Compose
  • Architecture
  • Testing
  • Mobile system design

I’ve tried to keep the answers practical instead of just giving definitions. Most questions have explanations, and examples. I personally find examples super helpful when learning anything.

There is also a section for real Android interview experiences. I want to keep adding more of those because I personally found those way more useful than generic “top 50 Android questions” articles.

It is free, no login, no paywall, no ads. MOST IMPORTANTLY, OPEN SOURCE.

Would honestly love feedback from the devs here:

  • What topics do you think are missing?
  • What Android interview questions keep coming up lately?
  • Is there anything on the site that feels unnecessary or annoying?

Just built this because I wanted a better place to prep and thought it might help others too. I am planning on switching my job and what better way than to do it while helping everyone else?

u/ilikeca — 16 hours ago
▲ 16 r/Kotlin+1 crossposts

Hikage - A real-time Android View runtime powered by Kotlin DSL

I have been writing Android long enough to feel the strange split in the UI world.

On one side, XML is boring in the best and worst ways. It is stable, deeply integrated with the platform, understood by every legacy custom View, and still works with the Android pipeline that has existed for years. On the other side, Jetpack Compose gives Kotlin developers a much better authoring model: UI as code, local composition, reusable functions, less ceremony.

But real projects are rarely clean rewrites.

A lot of Android apps still live in the View ecosystem. They have custom Views, AppCompat behavior, Material components, LayoutInflater.Factory2, old XML attributes, obtainStyledAttributes, ViewBinding, and code that cannot simply be deleted because a newer UI framework exists.

I wanted something in the middle.

Not "replace Compose". Not "keep writing XML forever". Something closer to: what if the classic Android View system could be authored like modern Kotlin code?

That became Hikage.

Hikage is a real-time Android View runtime powered by Kotlin DSL. Crucially, Hikage doesn't reinvent the wheel with a new UI component system. It acts as a transporter for the existing Android View ecosystem. It is more like a transporter for the existing Android View ecosystem.

A simple layout looks like this:

LinearLayout(
    lparams = LayoutParams(matchParent = true),
    init = {
        orientation = LinearLayout.VERTICAL
        gravity = Gravity.CENTER
    }
) {
    TextView {
        text = "Hello, World!"
        textSize = 16f
        gravity = Gravity.CENTER
    }
}

That part is nice, but honestly, syntax alone is not the interesting bit. Android has already had DSL attempts before. Anko existed. Splitties exists. Compose exists.

The part I cared about was whether a Kotlin DSL could still behave like a first-class citizen in the old View world.

For example, Hikage can mix with existing layouts instead of forcing a rewrite:

LinearLayout(
    lparams = LayoutParams(matchParent = true),
    init = {
        orientation = LinearLayout.VERTICAL
    }
) {
    Layout(R.layout.my_layout)
    Layout<MyLayoutBinding>()

    ComposeView {
        Text("Hello from Compose")
    }
}

And the bridge goes both directions: Hikage can host Compose, and Compose can host Hikage.

The bigger technical problem was XML attributes.

A lot of real Android Views are not designed to be fully configured by setters. They expect values in the constructor through AttributeSet, then call obtainStyledAttributes. XML gets this naturally because AAPT2 compiles the layout and LayoutInflater feeds the resulting parser into View(Context, AttributeSet).

A normal Kotlin DSL usually skips that path.

Hikage tries to enter it.

It can dynamically construct an AttributeSet at runtime, so this:

TextView(
    attrs = {
        android {
            set("text", "Set text in dynamic AttributeSet")
            set("textSize", "16sp")
            set("gravity", "center")
            set("paddingLeft", "8dp")
            set("paddingRight", 8.dp)
        }
    }
) {
    text = "Overridden text in code"
}

is not just setting properties after construction. It lets the View receive XML-style attributes during creation.

Internally, the runtime builds an in-memory XML-like structure, resolves attributes, separates layout_* attributes for parent LayoutParams, and then lets the View constructor / factory chain do what Android Views already know how to do.

The architecture is roughly:

Kotlin DSL
 -> LayoutSession
 -> optional runtime AttributeSet resolver
 -> HikageFactory / LayoutInflater.Factory2 bridge
 -> View(Context, AttributeSet)
 -> init block
 -> parent LayoutParams
 -> View tree

For traditional XML, the comparable path is:

XML layout
 -> AAPT2 compiled XML
 -> LayoutInflater
 -> XmlResourceParser / AttributeSet
 -> Factory2 / AppCompat interception
 -> View(Context, AttributeSet)
 -> View tree

That is the design idea: not bypassing the old platform, but meeting it where it already works.

There are some practical pieces around it too:

  • KSP can generate DSL functions for custom Views and third-party Views.
  • Declaration JSON files can describe external View components.
  • AndroidX and Material View declarations are provided as modules.
  • Android Studio preview is supported through a HikagePreview View.
  • There is lightweight state binding for View-based layouts. State changes mutate existing View instances instead of rebuilding the whole tree.
  • It can work with XML, ViewBinding, Compose, and plain Views in the same layout.
  • The runtime attribute module has been tested across Android 5.0.2 / API 21 through Android 17 / API 37 on emulators and real devices.

I do not want to oversell benchmarks, because that is not the main point. The main point is the architecture. The benchmark and compatibility reports are there so people can verify the claim instead of taking my word for it.

There are also tradeoffs.

The runtime XML attribute module uses reflection around XmlBlock, so if you ship to Google Play, you should evaluate that risk carefully. Hikage keeps it as a separate runtime extension instead of making it mandatory. If you only need the DSL layout runtime, you can use the core pieces without that module.

I built this because I think Android UI does not have to be a binary choice between "old XML forever" and "rewrite everything in Compose".

The View ecosystem is still huge. Compose is important. XML is still everywhere. There should be a middle state for teams that want Kotlin authoring, runtime layout construction, and compatibility with the Views they already have.

That middle state is what Hikage is trying to be.

GitHub: https://github.com/BetterAndroid/Hikage
Docs: https://betterandroid.github.io/Hikage/en
Architecture notes: https://betterandroid.github.io/Hikage/en/guide/architecture

I would be especially interested in feedback from people who maintain mixed View / Compose apps, custom View libraries, or large legacy Android codebases. The question I keep coming back to is: if View-based Android is not going away tomorrow, what should its modern authoring layer look like?

u/Educational_Hall_249 — 14 hours ago
▲ 25 r/Kotlin

What is your opinion about this code?

I'm tring to learn the best kotlin code practices and i code this but i don't know if a correct "sugar sintax"

u/jjcr03 — 1 day ago
▲ 56 r/Kotlin+1 crossposts

I was in for quite a surprise when I launched IntelliJ IDEA this morning. Happy birthday, Kotlin!

u/adjudant412 — 22 hours ago
▲ 44 r/Kotlin

Rotlin. Kotlin but for GenZ

Struggling to keep your kids/students engaged in learning Kotlin?

Introducing Rotlin.

No more memorizing normal syntax. Embrace the degeneracy and engage with the youth.

A statically typed, null-safe, object-oriented programming language for teaching web dev but with brainrot syntax.

Compiles to Kotlin so every Java/Kotlin library works

I should take my meds.

github.com
u/Erica192859 — 1 day ago
▲ 36 r/Kotlin

19 KotlinConf recordings are up

They are unlisted on YouTube, but you can get to ’em from the website.

I expect the rest are on the way.

Which one is your favorite?!

kotlinconf.com
u/swankjesse — 1 day ago
▲ 35 r/Kotlin+2 crossposts

I built offline-first sync for Android - Room stays yours, library handles outbox + push/pull.

I've been working on offline-first sync for Android apps that use Room.

Pattern: local write immediately → outbox queue → push/pull when online → handle conflicts if the same row changed on server.

Short demo (GIF): offline add → sync → wipe local DB → pull restores data.

Technical bits:

• Room stays the source of truth for entities

• Separate SQLDelight outbox (survives process death)

• KSP generates sync handlers from Entity/DAO

• Gradle plugin wires KSP + serialization

Stack is Kotlin 2.1, minSdk 24. Apache 2.0, sample + mock server in the repo.

Repo (if useful): https://github.com/Arsenoal/syncforge

Genuinely looking for architecture feedback:

  1. Would you trust a library-owned outbox next to Room, or keep everything in-app?
  2. What's the minimum you'd need before trying this in a non-toy app?

Happy to share Gradle setup in comments if anyone wants to poke at it.

u/Extra_Ninja_8101 — 2 days ago
▲ 32 r/Kotlin+1 crossposts

Probabilistic Programming Language Interpreter

Hi everyone!

I've been developing with Kotlin for many years, mostly building Android apps as a freelancer. Alongside that, I'm studying Computer Science, and I'm particularly interested in programming language theory.

One of the things I enjoy most about Kotlin is how naturally it supports multiple programming paradigms. I usually write Kotlin in a functional style, like an object-oriented version of Haskell.

I recently built an interpreter for a higher-order probabilistic programming language entirely in Kotlin.

The project is based on this book "An Introduction to Probabilistic Programming" and implements the Lisp-like language described there: https://arxiv.org/abs/1809.10756

I think one of the most interesting aspects of these language/s is the idea of checkpoints (sample and observe), which act as execution breakpoints that inference algorithms can intercept and control.

That mechanism is really the heart of the interpreter, and it's quite easy to handle in Kotlin.

I thought some people here might find this project interesting. It's a nice jump off the Android zone of this language.

Repository: https://github.com/LeoBrasileo/HOPPL-Interpreter

u/LeoBrasileo — 2 days ago
▲ 0 r/Kotlin

Is Kotlin becoming too bloated? Why I am sticking to a "Minimalist" subset of the language.

Hey kotlin devs,

I have been a developer for 2 years now, mostly using Kotlin for Android and Java for backend. After working with both, I feel like Kotlin is becoming unnecessarily complex because it’s trying to be everything at once.

Betwen deep OOP, functional programming, and massive async concepts like advanced Coroutines, it feels like there's just too much abstraction. Honestly, this is why I still love Java on the backend. Yes, it is verbose, but it is clean, explicit, and highly predictable. You always know exactly what the code is doing.

Lately, I decided to stop chasing complex feature in Kotlin because it just ruins my productivity. Instead, I have stripped my usage down to a simple, predictable subset:

  • Standard OOP and basic Kotlin concepts.
  • Basic functional concepts (like collection operations, Extension function, High Ordered Function).
  • Basic coroutines for simple background work.

I really think a language should be simple and powerful, not bloated with endless concepts.
Anyone else intentionally keep their Kotlin simple just to stay sane? Or am I missing something?

reddit.com
u/Reasonable-Tour-8246 — 3 days ago
▲ 0 r/Kotlin+1 crossposts

Kotlin Future in Backend Development

I see that new startups barely pick Java/Kotlin as backend language. They usually pick node.js/python/Go. We usually see Java in old companies, and these companies sometimes choose to write new stuff in Kotlin.

The old companies will somewhen get closed. If the new generations of companies do not pick Java/Kotlin, then the conclusion is that these languages will become obsolete (at least for backend) in the near future.

What do you think?

reddit.com
u/Sad_Importance_1585 — 3 days ago
▲ 0 r/Kotlin

which has better overall performance in this implementation?

between method1 and method2 which has better performance and lower overhead?

ChatGPT says method1 is slightly cheaper but Gemini says method2 is better.

(dont complain about semikolons in the comments, its harder to know if this line is the end of the statement or if it continues in the next line)

@file:JvmName("Suppliers")

import java.util.function.IntSupplier;

@JvmSynthetic
@JvmName("_intSupplierOf")
inline fun intSupplierOf(value: Int): IntSupplier = IntSupplier { value };

@JvmName("intSupplierOf")
fun _intSupplierOf(value: Int): IntSupplier = IntSupplier { value };

class Test {
	fun method1(): Int {
		val x = intSupplierOf(3);
		return x.getAsInt();
	}

	fun method2(): Int {
		val x = _intSupplierOf(3);
		return x.getAsInt();
	}
}

decompiled Java code for reference:

public final class Suppliers {
	// $FF: synthetic method
	@JvmName(
		name = "_intSupplierOf"
	)
	public static final IntSupplier _intSupplierOf(final int value) {
		int $i$f$_intSupplierOf = 0;
		return new IntSupplier() {
			public final int getAsInt() {
				return value;
			}
		};
	}

	@JvmName(
		name = "intSupplierOf"
	)
	@NotNull
	public static final IntSupplier intSupplierOf(int value) {
		return Suppliers::_intSupplierOf$lambda$0;
	}

	private static final int _intSupplierOf$lambda$0(int $value) {
		return $value;
	}
}

public final class Test {
	public final int method1() {
		int value$iv = 3;
		int $i$f$_intSupplierOf = false;
		IntSupplier x = (IntSupplier)(new 1(value$iv));
		return x.getAsInt();
	}

	public final int method2() {
		IntSupplier x = Suppliers.intSupplierOf(3);
		return x.getAsInt();
	}
}
reddit.com
u/Thyristor_Chopper — 3 days ago
▲ 2 r/Kotlin

MVI/UDF: Best way to model two flows?

I have an MVI/UDF feature with two entry flows: a pre-action flow and a post-action flow.

One idea I had for the initial step is to have a repository, or similar abstraction, determine whether the feature should start in the pre-action or post-action flow. The ViewModel would call this repository, which would return the initial flow along with the data needed to render the first UI state.

My team's existing preference is to model UI state using sealed classes, where only one screen state is active at a time. I'm thinking of following the same approach for the new flows we're building so the pattern stays consistent across the feature.

Example state shape:

sealed interface FeatureUiState {

    // Pre-action flow entry.
    // User selects the initial date and time.
    data class DateTimeSelection(
        val date: String,
        val time: String,
        val isNextEnabled: Boolean
    ) : FeatureUiState

    // Shared loading state while calling the validation/recalculation API.
    data object Loading : FeatureUiState

    // Pre-action flow.
    // Displayed after the API returns the calculated values.
    // One extra value is calculated locally before the user can save.
    data class EditPreparationTime(
        val preparationTime: String,
        val estimatedPickupTime: String,
        val estimatedDeliveryTime: String,
        val fireAtTime: String,
        val isConfirmEnabled: Boolean
    ) : FeatureUiState

    // Post-action flow.
    // User edits preparation time. One value is recalculated locally,
    // then the API recalculates the remaining values.
    // The same screen is shown again with the updated API values.
    data class AdjustPreparationTime(
        val preparationTime: String,
        val estimatedPickupTime: String,
        val estimatedDeliveryTime: String,
        val isNextEnabled: Boolean
    ) : FeatureUiState

    // Shared saving state.
    data object Saving : FeatureUiState

    // Shared API error state.
    // When dismissed, the entire flow restarts from the beginning.
    data class Error(
        val message: String
    ) : FeatureUiState
}

The flow would look like this:

Pre-action flow: DateTimeSelection → Loading → API success → EditPreparationTime → calculate local value → Saving

The user can also navigate back from EditPreparationTime to DateTimeSelection. When they do, the previously selected date and time should be preserved.

Another thing I'm unsure about is how to model this in MVI/UDF. Since the UI is modeled as a single active sealed state, once DateTimeSelection is replaced by Loading and then EditPreparationTime, the original DateTimeSelection state no longer exists.

When the user presses Back from EditPreparationTime, what is the idiomatic way to restore the previously selected date and time? The domain model may only contain the original/default values, so recreating the screen from the domain model would lose any changes the user made before navigating forward.

Should the ViewModel keep the last selected date/time as draft UI state, keep the previous DateTimeSelection state somewhere, or is there a better pattern for handling this while still keeping a single active sealed UI state?

Post-action flow: AdjustPreparationTime → Loading → API success → AdjustPreparationTime with updated values → Saving

Any API error: Loading / Saving → Error → Restart the entire flow

One part I'm unsure about is the shared "Loading" state.

Both flows call the same API, and the API response contains the same fields, such as preparation time, estimated pickup time, and estimated delivery time. However, after the API succeeds, the next state is different depending on which flow triggered the API call.

For the pre-action flow, API success should map to "EditPreparationTime", with one extra value calculated locally. For the post-action flow, API success should map back to "AdjustPreparationTime" with updated values.

Should the ViewModel keep track of the current flow before emitting "Loading", so it knows whether API success should map to "EditPreparationTime" or "AdjustPreparationTime"?

Or is it better to avoid a shared loading state and use more explicit states, for example "CalculatingEditPreparationTime" and "RecalculatingAdjustedPreparationTime", so the next state is clear from the state itself?

Has anyone implemented something similar? I'd appreciate any guidance on whether this is an idiomatic way to model this in MVI/UDF while still keeping a single active sealed UI state.

reddit.com
u/Classic_Jeweler_1094 — 3 days ago
▲ 10 r/Kotlin

What are you currently working on, and how's it going?

I'd love to hear about what people are doing. I've been working solo for a while and I miss the conversation with other coders. It is a weird and exciting time to be working on code, am I right?

reddit.com
u/TrespassersWilliam — 4 days ago
▲ 43 r/Kotlin

Kotlin support is now available in BlueJ 💙

Having reached more than 25 million learners worldwide, BlueJ is one of the most widely used environments for teaching introductory object-oriented programming.

Learn more and get started with Kotlin in BlueJ: https://kotl.in/rxtuke

u/Belosnegova — 5 days ago
▲ 0 r/Kotlin

Is learning Kotlin worth it? Where?

Hello, I have a question. I did a fullstack course but I didn’t like it, not my thing. My neighbour is working dev using kotlin, said it’s cool. I want to try smth new, but I want to get a job that I at least 50% will like

So if it’s popular and needed where can I learn it fast and good?

reddit.com
u/Confident_Stress_883 — 6 days ago
▲ 0 r/Kotlin

We've spent years fixing JVM startup. What if that's the wrong problem?

Our prod backend — Spring + Hibernate — had a ~25s cold start, on a system that scales out during traffic spikes. So a slow boot wasn't slow, it was user-visible downtime. In an R&D spike I built the same service on Kotlin Multiplatform: ~3s cold start on the JVM, under 0.3s compiled to Kotlin/Native — same code, two targets. That gap is why I think the whole JVM-startup arms race — CRaC, Leyden, GraalVM — is solving the wrong problem. I hit a wall worth arguing about.

To kill the obvious misread: I did not port that prod backend to native — it runs on the JVM and always will. The KMP build was a separate spike; it matured into something we could have shipped, then the company shrank and it never ran in prod.

We didn't skip the JVM's own startup playbook — GraalVM got the most serious look, and it's good tech that just didn't fit us: closed-world reflection config (even Ktor needs the CIO engine plus a JSON file from the tracing agent), AOT trading away the JIT on a workload that stays warm long enough to want it, heavy CI.

The main point. We've decided the fix for "the VM starts slowly" is an arms race to make the VM start fast. At some point it starts to feel like we'd rather rebuild the VM as a native compiler than admit the VM itself is the startup tax. Native doesn't pay that tax in the first place — that 0.3s boot, one binary, tiny footprint, nothing to install on the host. And 3s isn't "fine" here. When you scale to zero and back on a spike, cold start is on the request path, not hidden in a startup log. Even a tuned 3s is latency a user eats; 0.3s makes it disappear. That's not a tuning win — it removes the problem.

Then the wall: no JDBC on Native — and there can't be, JDBC is a JVM standard, which means no mature ORM on top of it either. Grown-up native ecosystems solved this by reimplementing the Postgres wire protocol in their own language: Go has pgx, Rust has sqlx / tokio-postgres, no libpq. Kotlin/Native has almost none of that, so I bound libpq through cinterop to get something working. That was where I stopped looking for an ORM and accidentally started writing one. I kept solving the next missing piece until I had a full data layer. That project eventually became Kormium. I'll leave the link in the comments for anyone who's curious.

Concurrency is where I expect hits. On the JVM the answer is Loom: block on a JDBC call, the carrier thread parks cheaply, your ceiling is the connection pool. Native has no Loom, but libpq has an async API, so you can drive it off a poll-based socket reactor and get genuinely non-blocking suspend with no VM underneath.

No flag-waving: I'm not claiming native is faster. Rust handles load better, a warm JIT beats AOT on sustained throughput. Native wins on startup, footprint, and not shipping a VM. K/N's realistic ceiling is Go's niche, not Rust's.

Here's the take I want proven wrong: Kotlin/Native isn't adopted on the backend because backend performance still isn't compelling enough → nobody builds the ecosystem (the missing data layer is the hole I fell into) → no pressure on JetBrains → performance doesn't improve → nobody adopts it. The root is performance, the rest is symptoms.

So — tell me where I'm wrong. Do CRaC/GraalVM genuinely close the gap, making native on the server unnecessary? Or is the direction right and JetBrains just digging in the wrong place? And if you've run K/N (or Go/Rust) on a backend, tell me where it broke — that's the data we're all missing.

reddit.com
u/spokimono — 6 days ago
▲ 13 r/Kotlin

Meet KetraTerm: a modern, high-performance terminal

I have released KetraTerm 0.1.0, a modern open-source terminal emulator implemented entirely in Kotlin.

It is available as:

  • a standalone desktop terminal for Windows, macOS, and Linux;
  • a published JetBrains IDE plugin;
  • reusable Kotlin/JVM terminal libraries.

KetraTerm does not depend on JediTerm and does not wrap another terminal engine. It includes its own streaming parser, UTF-8 decoder, terminal grid, scrollback model, Unicode handling, keyboard encoder, PTY integration, rendering pipeline, shell integration, and host security layer.

The main challenge: making a JVM terminal fast

A terminal processes a huge number of very small state changes.

A naive JVM implementation can easily allocate an object for every cell, produce temporary collections while parsing, repaint the whole terminal after minor updates, and put constant pressure on the garbage collector.

KetraTerm was designed around a different architecture.

The terminal grid uses compact primitive-backed storage rather than an object-per-cell model. Frequently executed parsing and mutation paths are designed for a low allocation profile.

The renderer tracks dirty regions and redraws only the content that actually changed.

Terminal mutation, frame publication, and UI painting are decoupled, allowing the parser to continue consuming output while the UI renders stable frames.

This architecture is designed to remain responsive during:

  • heavy command output;
  • rapidly updating TUIs;
  • agentic development tools and AI CLIs;
  • large builds;
  • verbose logs;
  • continuously updating dashboards;
  • long-running sessions where GC pressure matters.

Modern terminal capabilities

KetraTerm currently supports:

  • Unicode 17.0;
  • grapheme clusters and combining characters;
  • wide, zero-width, and ambiguous-width characters;
  • complex emoji sequences and fallback fonts;
  • gap-free box-drawing and block rendering;
  • 24-bit TrueColor;
  • modern underline styles and underline colors;
  • alternate screen buffers;
  • modern mouse tracking;
  • bracketed paste;
  • Kitty keyboard protocol;
  • CSI-u;
  • xterm modifyOtherKeys;
  • OSC 133 and OSC 7 shell integration;
  • OSC 9 and OSC 777 desktop notifications.

The Unicode implementation is terminal-aware rather than merely UTF-8-aware. Grapheme segmentation and display width are handled as part of the fixed-cell terminal model.

Modular JVM architecture

KetraTerm is split into reusable modules for parsing, terminal state, input encoding, host operations, sessions, PTY transport, rendering, Swing integration, and workspace management.

The same terminal core powers both the standalone desktop application and the JetBrains plugin.

The project is available under the Apache 2.0 license, and development is active. New features, compatibility fixes, performance improvements, and API refinements will continue to be released.

I would especially appreciate feedback from Kotlin and JVM developers about the architecture, performance characteristics, public APIs, and possible embedding use cases.

I would also be interested in real-world workloads that are difficult for JVM applications: very heavy output, rapid redraws, large scrollback, complex Unicode, or demanding TUI applications.

GitHub:
https://github.com/ketraterm/KetraTerm

Website:
https://ketraterm.github.io/KetraTerm/

JetBrains plugin:
https://plugins.jetbrains.com/plugin/32589-ketraterm

u/gagik894 — 6 days ago
▲ 35 r/Kotlin

Argentum — a Magic: The Gathering rules engine in Kotlin

Magic has over 28,000 unique cards and one of the most notoriously complex rules systems in games. I've been building an engine that implements it properly.

The core is a pure function: (GameState, GameAction) -> (GameState, List<GameEvent>). State is fully immutable, modeled as an ECS. There's no card-specific logic in the engine at all — the hard machinery (the 613 layer system, the stack, priority, replacement effects, state-based actions) lives in one place, and cards are pure data defined through a Kotlin DSL.

The whole thing is built as four layers, each with a strict responsibility:

- SDK — the data contract. Cards, effects, targets, and dynamic values are all pure, serializable data structures — no execute(), no lambdas. A dynamic value like Tarmogoyf's power is an AST node (DynamicAmount), not a (GameState) -> Int, so it can be serialized, inspected for UI text, and lazily evaluated. This layer defines what happens.
- Rules engine — the functional core. A zero-dependency Kotlin library (no Spring, no I/O) that interprets that data. Immutable GameState, ECS entities, and — because it can't block a thread waiting on network input mid-resolution — reentrant, serializable continuations: when a spell needs a choice ("search your library"), it pauses, pushes a continuation frame, and resumes when the answer arrives. This layer defines how.
- Game server — thin Spring Boot orchestration with zero game logic. It routes WebSocket messages, manages sessions, and does server-side state masking (fog of war) so a modified client can't read your opponent's hand. It's an anti-corruption layer: the engine's GameState never leaves the server, only client-safe DTOs.
- Web client — a dumb terminal. No game rules, no legal-action computation. It renders what the server sends and captures intent; the server is authoritative for everything.

The payoff of the layering: the engine is a pure library, so it's exhaustively testable without a server, and it's portable — the same engine also powers a Gymnasium-style RL/MCTS wrapper for agent research, with no server or browser involved.

It's playable at magic.wingedsheep.com — booster draft tournaments, multiplayer, up to 6-player free-for-all.

Most of it was written with Claude Code under a strict architecture I kept it to. Source and a writeup below

Repo: https://github.com/wingedsheep/argentum-engine
Writeup: https://wingedsheep.com/building-argentum-a-magic-the-gathering-rules-engine/
Discord Channel: https://discord.com/invite/dy6eSRPWzu

reddit.com
u/wingedsheep38 — 6 days ago
▲ 40 r/Kotlin+2 crossposts

Switching a million lines of code from Java threads to Kotlin coroutines, by rewriting three files

I wrote a technical deep dive about how we migrated one of Denmark's most used Android apps with one million lines of code powered by Java threads, into Kotlin coroutines just by rewriting three files in our internal threading library

Check it out if you are interested in how coroutines use threads, and interop between them :)

medium.com
u/adrianblancode — 6 days ago