Result Type: What It Is, the Result Container Type and How It Works in Mobile Development

Author: IT Sectr Published: 2026-05-26 Reading time: 9 min

Result Type — a container type that represents the result of an operation that can complete with success (Success) or failure (Failure). Unlike exceptions, Result passes the error as a regular value without stack unwinding, making expected error handling safer and more composable. According to Apple Swift Documentation (2026), Result<Success, Failure> in Swift allows chaining operations with automatic error handling through map and flatMap without interrupting program execution.

Key Takeaways

  • Result Type — a generic container with two states: Success (data) and Failure (error), without stack unwinding
  • In Swift Result<Success, Failure> — a built-in type with map, flatMap, mapError, and get methods
  • In Kotlin Result<T> represents success or Throwable, with getOrNull, getOrDefault, fold functions
  • In Dart standard Result<T> is available since Dart 3.0, also Either from the fpdart package
  • Composition through map and flatMap allows combining multiple operations that return Result without nested checks

What Is Result Type?

Result Type is a generic type that encapsulates the outcome of an operation that can either succeed or fail. Unlike exceptions, where an error interrupts the normal flow and requires stack unwinding to find a catch block, Result passes the error as a regular value — the caller always receives an object and decides how to handle it. This is especially useful for expected errors: invalid input, business rules, server rejection, where exceptions would be too heavy a mechanism.

The concept of Result originates from functional programming, where similar types are called Either (or Left/Right). In Swift, Result became a standard type in Swift 5.0; in Kotlin, Result<T> appeared in the standard library; in Dart 3.0, built-in Result<T> was introduced. Each implementation provides methods for working with the container: map (transform the success value), flatMap (chain Result functions), mapError (transform the error), fold (handle both cases). Result Type is a cornerstone of functional error handling in mobile development, an alternative to try-catch for expected scenarios.

The key advantage of Result is composability. You can combine multiple operations, each of which may fail, into a single chain without nested try-catch blocks. If any operation in the chain fails with Failure, the entire chain is interrupted and returns Failure — without a single if condition or catch block. This makes the code linear and readable, especially in scenarios with multiple sequential API requests or business rule checks.

Result in Swift

In Swift, Result<Success, Failure> is an enum with two cases: .success(Success) and .failure(Failure), where Failure is constrained by the Error protocol. Swift Result is a fully functional type with map, flatMap, mapError, and get methods. get is a special method: it returns the Success value if the result is successful, and throws the error if it is Failure. This allows Result to act as a bridge between functional and exception-based styles: handle through map/flatMap in a chain, and at the end use get with do-catch to integrate with throws code.

Result enum and exhaustive switch

Swift Result is an enum with generic parameters, which allows the compiler to enforce exhaustive handling through switch or do-catch. If you add a new case to NetworkError enum, the compiler will produce an error in all switch expressions where that case is not handled. Exhaustive checking is the main advantage of Result over exceptions: the compiler guarantees that all possible errors are accounted for at build time. In contrast, exceptions are not checked by the compiler in Swift (only throws is declared, but not the error type).

swift
enum NetworkError: Error {
    case badURL
    case requestFailed(String)
    case decodingFailed
}

func fetchUser(id: Int) -> Result<User, NetworkError> {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        return .failure(.badURL)
    }
    let result = performRequest(url: url)
    switch result {
    case let .success(data):
        if let user = try? JSONDecoder().decode(User.self, from: data) {
            return .success(user)
        }
        return .failure(.decodingFailed)
    case let .failure(error):
        return .failure(.requestFailed(error.localizedDescription))
    }
}

// Usage with switch
let result = fetchUser(id: 42)
switch result {
case .success(let user):
    showUser(user)
case .failure(.badURL):
    logError("Invalid URL")
case .failure(.requestFailed(let msg)):
    showAlert(msg)
case .failure(.decodingFailed):
    logError("Decoding error")
}

Result<Success, Failure> allows typing the error at the type level: the fetchUser function returns Result<User, NetworkError>, where NetworkError is a concrete enum with three cases. The caller handles each case via switch with exhaustive coverage — the compiler checks that all variants are handled. Exhaustive switch is a key advantage of Result over exceptions: the compiler guarantees you won’t forget to handle .badURL, .requestFailed, or .decodingFailed. With exceptions, the compiler does not require handling, and a missing catch(.badURL) can easily slip through code review.

Result in Kotlin

In Kotlin, Result<T> is a built-in type from the standard library that represents success (T) or error (Throwable). Unlike Swift, Kotlin Result does not allow specifying a concrete error type — only Throwable. This is done for simplicity but requires additional error type checking via when expression. Kotlin Result supports fold (handle both cases), getOrNull (success or null), getOrDefault (success or default), map, recover, andThen. A peculiarity of Kotlin: Result is not intended for direct propagation across function boundaries — it cannot be used as a return type for Android SDK methods or Kotlin Coroutines API without additional adaptation.

kotlin
fun parseJson(input: String): Result<JsonObject> {
    return runCatching {
        JsonParser.parseString(input).asJsonObject()
    }
}

fun validateEmail(email: String): Result<String> {
    return if (email.contains("@")) {
        Result.success(email.trim())
    } else {
        Result.failure(IllegalArgumentException("Invalid email"))
    }
}

data class SignupData(val name: String, val email: String)

fun processSignup(name: String, email: String): SignupResult {
    return validateEmail(email).fold(
        onSuccess = { validateName(name) },
        onFailure = { SignupResult.Error("Invalid email") }
    )
}

runCatching is a convenient wrapper in Kotlin that catches any exception and returns Result.failure. validateEmail returns Result.success or Result.failure depending on validation. fold handles both cases compactly: on Success, the next validation function is called; on Failure, SignupResult.Error is returned. Important: Kotlin Result is not intended for storage in data class fields or direct transmission across suspend function boundaries — use custom sealed classes (Success/Error/Loading) to represent UI states in Jetpack Compose or MVVM.

Result in Dart and Flutter

Since Dart 3.0, the standard library includes a built-in Result<T> — a sealed class with two constructors: T.ok() (success) and Error.error() (error with Object and StackTrace). Before Dart 3.0, Flutter developers used Either<L, R> from the dartz package or custom sealed classes. The built-in Result in Dart is minimalistic: it does not provide map/flatMap directly — these functions need to be implemented via when or using extension methods. For serious functional handling, Either from fpdart remains a more powerful solution with support for map, flatMap, mapLeft, fold, andThen, and bind operators.

Either from fpdart

Either<L, R> is a left-biased type from the fpdart package, where Left is the error and Right is the success. Unlike the built-in Result<T>, Either types the error at the type parameter level (L), allowing error type differentiation at compile time. The fpdart package provides a full set of functional combinators: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — nested Either), andThen (chain without transformation), fold (exit from Either), getOrElse (default value). For Flutter applications with a functional approach, Either is the de facto standard.

dart
import 'dart:convert';
import 'package:fpdart/fpdart.dart';

class UserService {
    Either<AppError, User> fetchUser(String id) {
        try {
            final response = await http.get(
                Uri.parse('https://api.example.com/users/$id')
            );
            if (response.statusCode == 200) {
                final user = User.fromJson(
                    json.decode(response.body)
                );
                return Either.of(user);
            }
            return Either.left(
                AppError.serverError(response.statusCode)
            );
        } on SocketException catch (e) {
            return Either.left(AppError.networkError(e.message));
        }
    }
}

// Usage with fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> from fpdart is a left-biased type: Left — error, Right — success. fetchUser returns Either<AppError, User>, where AppError is a sealed class with concrete error types (serverError, networkError). fold handles both cases: the first callback for Left (error), the second for Right (success). In Dart 3.0, the built-in Result also supports fold, but does not provide map/flatMap. For chains, Either from fpdart provides map, flatMap (bind), mapLeft, andThen — a full set of functional combinators for error composition.

Result Composition via map and flatMap

The main advantage of Result over exceptions is composition. If you have multiple operations, each of which may fail, you can chain them using map and flatMap without a single nested if or try-catch. map transforms the success value: Result.success(x) -> Result.success(f(x)). flatMap (also called bind or andThen) is for cases where the transformation itself returns a Result: Result.success(x) -> f(x) -> Result<Y>. If any step fails with Failure, subsequent operations are not executed — the chain is interrupted.

kotlin
data class UserRequest(val userId: String, val token: String)

sealed class AuthError {
    data object InvalidToken : AuthError()
    data class UserNotFound(val id: String) : AuthError()
}

typealias Outcome<T> = Either<AuthError, T>

fun validateToken(token: String): Outcome<String> =
    if (token.isNotBlank()) Either.right(token)
    else Either.left(AuthError.InvalidToken)

fun fetchProfile(userId: String): Outcome<Profile> =
    if (userId == "42") Either.right(Profile("Alice"))
    else Either.left(AuthError.UserNotFound(userId))

// Composition via flatMap (andThen in fpdart)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

Chain: validateToken -> fetchProfile -> map name -> getOrElse “Guest”. If validateToken returns Left (InvalidToken), the chain is interrupted and returns “Guest”. If fetchProfile returns Left (UserNotFound) — also “Guest”. If both operations succeed — the profile name. flatMap allows combining Either functions, each of which may fail, into a single linear chain. In the traditional exception style, the same code would require two nested try-catch or null checks. getOrElse at the end is the exit point from the composition, providing a default value for the Failure case.

Result vs Exceptions: When to Choose What

Result and exceptions are not mutually exclusive approaches. Each has its own domain, and in a well-designed mobile application, both are used. The choice depends on whether the error is expected or unexpected. Result is for expected errors that are part of business logic: invalid email, insufficient funds, rate limit exceeded. Exceptions are for unexpected system errors: network loss, OutOfMemoryError, NullPointerException (which should not happen but do occur).

CriterionResult TypeExceptions (Exception/Error)
Error TypeExpected (business logic)Unexpected (system)
PerformanceLow cost (no stack unwinding)High cost (stack unwinding, StackTrace capture)
CompositionVia map/flatMap — linear chainsNested try-catch — hard to read
CompilerExhaustive checking (switch/when)Only checked exceptions in Java
Execution FlowNot interrupted — error as a valueInterrupted until nearest catch
TestingEasy: check result, assert isSuccess/isErrorRequires assertThrows and mock objects
When to UseBusiness validation, request chains, formsNetwork loss, I/O errors, system crashes

Practical rule: if the error is part of the normal application workflow (user entered an invalid email, insufficient permissions) — use Result. If the error is an exceptional situation (server not responding, out of memory) — use exceptions. In mobile development, Result at layer boundaries (UseCase -> ViewModel) and exceptions within layers (API -> Repository) is a common pattern that combines the advantages of both approaches.

Migration Strategy from Exceptions to Result

Migrating existing exception-based code to Result should be gradual. Start with layer boundaries: wrap throws function calls in Result { try ... } (Swift) or runCatching { ... } (Kotlin). Then replace the return type of Repository and UseCase methods with Result/Either, keeping the internal implementation on exceptions. In the final step, migrate the ViewModel: instead of UiState with exceptions, use a sealed class UiState<T> (Loading, Success, Error), where Error stores a domain error, not a Throwable. Gradual migration allows testing each layer separately without a global refactoring.

Frequently Asked Questions

How is Result Type different from Optional/Option?

Optional (T?) represents the presence or absence of a value — nil means “no data” but does not explain why. Result (Success/Failure) contains not only success but also the error reason with a concrete type. Use Optional when absence is normal (e.g., an optional profile field), and Result when you need error information.

How to convert a throws function to Result?

In Swift, use Result { try throwingFunc() } — the Result constructor takes a throwing closure. In Kotlin, use runCatching { throwingFunc() }, which returns Result<T>. In Dart, use Result<T>.tryCatch(() => throwingFunc()). This allows easy integration of exception-based code into Result chains.

How to handle an error in Result without losing information?

Use mapError (Swift) or mapLeft (Either in Dart/Kotlin) to transform the error type without changing the success value. If you need to handle both cases and return a single value, use fold. For logging without interrupting the chain, use onFailure (Kotlin) or a prefix inspection point.

Can Result be used in Kotlin with coroutines?

Yes, but with caution. Kotlin Result<T> is not recommended as a return type for suspend functions directly due to K2 compiler and reflection peculiarities. Use your own sealed class NetworkResult<T> (Success, Error, Loading) to represent states in coroutines. For expected errors in business logic, Either from Arrow is a more powerful alternative.

What is fold in the context of Result?

fold is a method that takes two callbacks: onSuccess (for the success case) and onFailure (for the error case), and returns a single value of any type. It is the equivalent of a switch expression but as a higher-order function. fold is the main exit point from Result chains, where you convert Success/Failure into UiState, a user-facing string, or another Result.

Summary

  • Result Type — a generic container (Success/Failure) for safe handling of expected errors without stack unwinding
  • In Swift Result<Success, Failure> with Failure: Error provides exhaustive checking via switch with type-safe errors
  • In Kotlin Result<T> wraps success or Throwable, runCatching is a convenient constructor from throws code
  • In Dart built-in Result<T> (Dart 3.0) and Either<L, R> from fpdart for advanced composition with map/flatMap
  • Composition via map (transform success) and flatMap (chain Result functions) replaces nested try-catch
  • Result vs exceptions: Result for expected business errors, exceptions for unexpected system failures
  • Use Result at layer boundaries for explicit and testable error handling without interrupting execution flow

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also