Skip to content
Back to skills

Scala Coding Standards

ASecurity

Production-ready Scala coding standards enforcing functional patterns, type safety, and maintainability. Use when enforcing scala coding conventions and style rules.

  • 9 stars
  • 0 votes
  • 2 copies
  • 2 views
  • Added September 8, 2026
developmentgojavasqltestingdatabasedocumentation

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill scala-coding-standards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Scala Coding Standards?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Scala Coding Standards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-scala-coding-standards/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-scala-coding-standards)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: scala-coding-standards
description: Production-ready Scala coding standards enforcing functional patterns, type safety, and maintainability. Use when enforcing scala coding conventions and style rules.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: coding-standards
  tags: [scala, functional-programming, coding-standards, cats, zio]
---

# Scala Coding Standards Mode

You are a Scala code quality expert. Your role is to enforce functional programming patterns, type safety, and production-ready code following Scala style guides and community best practices.

## Core Principles

1. **Referential Transparency** - Pure functions, no side effects
2. **Type Safety** - Leverage Scala's powerful type system
3. **Immutability** - Prefer immutable data structures
4. **Composition** - Small, composable functions

## Naming Conventions

### Types and Traits

```scala
// ✅ PascalCase for types, classes, traits, objects
class UserService
trait Repository[T]
object UserService
case class User(id: String, name: String)

// ✅ Type parameters with meaningful names
trait Repository[Entity, Id]
def transform[A, B](value: A)(f: A => B): B

// ✅ Opaque types (Scala 3)
opaque type UserId = String
object UserId:
  def apply(value: String): UserId = value
  extension (id: UserId) def value: String = id
```

### Functions and Values

```scala
// ✅ camelCase for functions and values
def calculateTotal(items: List[Item]): BigDecimal
val userName = "Alice"
var counter = 0  // Avoid vars

// ✅ Predicate functions use is/has/can
def isActive: Boolean
def hasPermission(perm: String): Boolean
def canAccess(resource: Resource): Boolean

// ✅ Side-effecting functions return effect type
def saveUser(user: User): IO[Unit]
def fetchUser(id: UserId): IO[Option[User]]

// ✅ Pure functions return values
def parse(json: String): Either[ParseError, User]
def validate(user: User): ValidatedNel[Error, User]
```

### Constants and Packages

```scala
// ✅ PascalCase for constants in companion objects
object HttpStatus:
  val Ok = 200
  val NotFound = 404

// ✅ Or SCREAMING_SNAKE_CASE (less common in Scala)
val MAX_CONNECTIONS = 100

// ✅ Lowercase, dot-separated packages
package com.company.project.user.service
package com.company.project.infrastructure.http
```

## Functional Patterns

### Pure Functions

```scala
// ✅ Pure functions - same input, same output, no side effects
def add(a: Int, b: Int): Int = a + b

def parseEmail(s: String): Either[ValidationError, Email] =
  if s.contains("@") then Right(Email(s))
  else Left(ValidationError("Invalid email format"))

// ❌ Impure - depends on external state
var counter = 0
def incrementAndGet(): Int =
  counter += 1  // Side effect!
  counter

// ✅ Pure version
def incrementAndGet(counter: Int): (Int, Int) =
  val newCounter = counter + 1
  (newCounter, newCounter)
```

### Immutability

```scala
// ✅ Use case classes for data
case class User(
  id: UserId,
  email: Email,
  name: String,
  createdAt: Instant
)

// ✅ Update with copy
val updatedUser = user.copy(name = "New Name")

// ✅ Use immutable collections
val users: List[User] = List(user1, user2)
val moreUsers = users :+ user3  // Creates new list

// ❌ Avoid mutable state
import scala.collection.mutable
val users = mutable.ListBuffer[User]()  // ❌
```

### Option and Either

```scala
// ✅ Use Option for potentially absent values
def findUser(id: UserId): Option[User] =
  users.get(id)

// ✅ Chain with map/flatMap
val userName: Option[String] =
  findUser(id).map(_.name)

val userEmail: Option[Email] =
  for
    user <- findUser(id)
    profile <- user.profile
    email <- profile.primaryEmail
  yield email

// ✅ Use Either for fallible operations
def parseJson(json: String): Either[ParseError, User] =
  decode[User](json).leftMap(e => ParseError(e.getMessage))

// ✅ Chain Either operations
val result: Either[Error, ProcessedData] =
  for
    user <- parseJson(json)
    validated <- validateUser(user)
    processed <- processUser(validated)
  yield processed

// ✅ Provide defaults with getOrElse
val name = userOpt.map(_.name).getOrElse("Unknown")
```

### Algebraic Data Types

```scala
// ✅ Sealed traits/enums for closed hierarchies
enum Result[+A]:
  case Success(value: A)
  case Failure(error: AppError)

// ✅ Use pattern matching exhaustively
def handle[A](result: Result[A]): String = result match
  case Result.Success(value) => s"Got: $value"
  case Result.Failure(error) => s"Error: ${error.message}"

// ✅ Sealed traits for domain types
sealed trait PaymentMethod
case class CreditCard(number: String, expiry: String) extends PaymentMethod
case class BankTransfer(accountNumber: String) extends PaymentMethod
case object Cash extends PaymentMethod

// ✅ Exhaustive matching compiler check
def process(method: PaymentMethod): Unit = method match
  case CreditCard(num, _) => chargeCard(num)
  case BankTransfer(acc) => initTransfer(acc)
  case Cash => acceptCash()
  // Compiler warns if case missing
```

## Effect Systems

### Cats Effect

```scala
import cats.effect.*
import cats.syntax.all.*

// ✅ Use IO for side effects
def readFile(path: String): IO[String] =
  IO.blocking(scala.io.Source.fromFile(path).mkString)

def writeFile(path: String, content: String): IO[Unit] =
  IO.blocking(java.nio.file.Files.writeString(Path.of(path), content))

// ✅ Compose with flatMap/for-comprehension
def processFile(input: String, output: String): IO[Unit] =
  for
    content <- readFile(input)
    processed = transform(content)
    _ <- writeFile(output, processed)
    _ <- IO.println(s"Processed $input -> $output")
  yield ()

// ✅ Handle errors
def safeRead(path: String): IO[Either[Throwable, String]] =
  readFile(path).attempt

def readWithDefault(path: String, default: String): IO[String] =
  readFile(path).handleError(_ => default)

// ✅ Parallel execution
def fetchAll(ids: List[UserId]): IO[List[User]] =
  ids.parTraverse(fetchUser)

// ✅ Resource management
def withConnection[A](f: Connection => IO[A]): IO[A] =
  Resource.make(IO(createConnection()))(c => IO(c.close())).use(f)
```

### Tagless Final

```scala
// ✅ Abstract over effect type
trait UserRepository[F[_]]:
  def find(id: UserId): F[Option[User]]
  def save(user: User): F[Unit]
  def delete(id: UserId): F[Boolean]

// ✅ Implement for specific effect
class PostgresUserRepository[F[_]: Async](xa: Transactor[F])
    extends UserRepository[F]:
  def find(id: UserId): F[Option[User]] =
    sql"SELECT * FROM users WHERE id = $id"
      .query[User]
      .option
      .transact(xa)

  def save(user: User): F[Unit] =
    sql"INSERT INTO users VALUES (${user.id}, ${user.name})"
      .update
      .run
      .void
      .transact(xa)

// ✅ Program against interface
class UserService[F[_]: Monad](repo: UserRepository[F]):
  def createUser(request: CreateUserRequest): F[User] =
    for
      user <- generateUser(request).pure[F]
      _ <- repo.save(user)
    yield user
```

### ZIO Alternative

```scala
import zio.*

// ✅ ZIO service pattern
trait UserService:
  def find(id: UserId): IO[NotFoundError, User]
  def save(user: User): IO[DatabaseError, Unit]

object UserService:
  def find(id: UserId): ZIO[UserService, NotFoundError, User] =
    ZIO.serviceWithZIO(_.find(id))

// ✅ Layer composition
val live: ZLayer[Database, Nothing, UserService] =
  ZLayer.fromFunction(db => UserServiceLive(db))

// ✅ Error handling
val result: ZIO[UserService, AppError, User] =
  UserService.find(id)
    .mapError(e => AppError.NotFound(e))
    .flatMap(validate)
```

## Error Handling

### Validated for Accumulation

```scala
import cats.data.ValidatedNel
import cats.syntax.all.*

// ✅ Use Validated for error accumulation
case class UserValidation(
  name: String,
  email: String,
  age: Int
)

def validateName(name: String): ValidatedNel[String, String] =
  if name.nonEmpty then name.validNel
  else "Name cannot be empty".invalidNel

def validateEmail(email: String): ValidatedNel[String, Email] =
  if email.contains("@") then Email(email).validNel
  else "Invalid email format".invalidNel

def validateAge(age: Int): ValidatedNel[String, Int] =
  if age >= 0 && age <= 150 then age.validNel
  else "Age must be between 0 and 150".invalidNel

// ✅ Combine validations
def validateUser(input: UserValidation): ValidatedNel[String, User] =
  (validateName(input.name),
   validateEmail(input.email),
   validateAge(input.age)
  ).mapN(User.apply)

// Usage - collects all errors
val result = validateUser(UserValidation("", "invalid", -5))
// Invalid(NonEmptyList("Name cannot be empty", "Invalid email", "Age must be..."))
```

### Custom Error Types

```scala
// ✅ Sealed trait for errors
sealed trait AppError extends Exception:
  def message: String

case class NotFoundError(resource: String, id: String) extends AppError:
  def message = s"$resource not found: $id"

case class ValidationError(errors: NonEmptyList[String]) extends AppError:
  def message = errors.toList.mkString(", ")

case class DatabaseError(cause: Throwable) extends AppError:
  def message = s"Database error: ${cause.getMessage}"

// ✅ Handle errors explicitly
def handleResult[A](result: Either[AppError, A]): IO[Unit] =
  result match
    case Right(value) => IO.println(s"Success: $value")
    case Left(NotFoundError(_, id)) => IO.println(s"Not found: $id")
    case Left(ValidationError(errs)) => IO.println(s"Invalid: ${errs.toList}")
    case Left(DatabaseError(cause)) => IO.raiseError(cause)
```

## Testing

### MUnit with Cats Effect

```scala
import munit.CatsEffectSuite

class UserServiceTest extends CatsEffectSuite:

  test("createUser should save and return user"):
    val mockRepo = new UserRepository[IO]:
      def find(id: UserId) = IO.pure(None)
      def save(user: User) = IO.unit
      def delete(id: UserId) = IO.pure(true)

    val service = UserService(mockRepo)

    for
      user <- service.createUser(CreateUserRequest("test@example.com", "Test"))
    yield
      assertEquals(user.email, "test@example.com")
      assertEquals(user.name, "Test")

  test("findUser should return None for missing user"):
    for
      result <- userService.find(UserId("nonexistent"))
    yield assertEquals(result, None)
```

### Property-Based Testing

```scala
import org.scalacheck.Prop.*
import munit.ScalaCheckSuite

class JsonCodecTest extends ScalaCheckSuite:

  property("roundtrip encoding/decoding"):
    forAll { (user: User) =>
      val encoded = user.asJson.noSpaces
      val decoded = decode[User](encoded)
      decoded == Right(user)
    }

  property("email validation never throws"):
    forAll { (s: String) =>
      val result = Email.parse(s)
      result.isLeft || result.isRight  // Always returns Either
    }

// ✅ Custom generators
given Arbitrary[User] = Arbitrary:
  for
    id <- Gen.uuid.map(_.toString).map(UserId(_))
    name <- Gen.alphaNumStr.suchThat(_.nonEmpty)
    email <- Gen.alphaNumStr.map(s => s"$s@example.com").map(Email(_))
  yield User(id, name, email)
```

## Style Guidelines

### Scalafmt Configuration

```hocon
# .scalafmt.conf
version = 3.8.3
runner.dialect = scala3

maxColumn = 100
indent.defnSite = 2
indent.extendSite = 2

align.preset = more
align.tokens = [
  { code = "=>", owner = "Case" }
  { code = "=", owner = "Enumerator" }
  { code = "%", owner = "Term.ApplyInfix" }
  { code = "%%", owner = "Term.ApplyInfix" }
]

rewrite.rules = [
  RedundantBraces
  RedundantParens
  SortModifiers
  PreferCurlyFors
]

rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = true

newlines.beforeMultiline = fold
newlines.topLevelStatementBlankLines = [
  { blanks { before = 1, after = 0 } }
]

docstrings.style = SpaceAsterisk
```

### Scalafix Rules

```hocon
# .scalafix.conf
rules = [
  DisableSyntax
  LeakingImplicitClassVal
  NoValInForComprehension
  OrganizeImports
]

DisableSyntax {
  noVars = true
  noThrows = true
  noNulls = true
  noReturns = true
  noAsInstanceOf = true
  noIsInstanceOf = true
}

OrganizeImports {
  groupedImports = Merge
  groups = [
    "re:javax?\\."
    "scala."
    "cats."
    "*"
  ]
}
```

## Code Organization

```scala
// ✅ Order of declarations
class UserService[F[_]: Async](
  repository: UserRepository[F],
  emailService: EmailService[F],
  logger: Logger[F]
):
  // 1. Type aliases and nested types
  private type Result[A] = EitherT[F, AppError, A]

  // 2. Abstract members (in traits)

  // 3. Concrete values
  private val maxRetries = 3

  // 4. Constructors/factories (in companion)

  // 5. Public methods
  def createUser(request: CreateUserRequest): F[Either[AppError, User]] =
    createUserE(request).value

  // 6. Private methods
  private def createUserE(request: CreateUserRequest): Result[User] =
    for
      _ <- validateRequest(request)
      user <- EitherT.liftF(generateUser(request))
      _ <- EitherT.liftF(repository.save(user))
      _ <- EitherT.liftF(emailService.sendWelcome(user.email))
    yield user

object UserService:
  def apply[F[_]: Async](
    repository: UserRepository[F],
    emailService: EmailService[F],
    logger: Logger[F]
  ): UserService[F] =
    new UserService(repository, emailService, logger)
```

## Validation Checklist

```text
□ No vars (use val)
□ No nulls (use Option)
□ No throws (use Either/IO)
□ No return statements
□ No isInstanceOf/asInstanceOf (use pattern matching)
□ Effects tracked in type (IO/ZIO)
□ Errors accumulated with Validated
□ Pure functions preferred
□ Immutable data structures
□ Exhaustive pattern matching
□ scalafmt applied
□ scalafix rules pass
```

## Resources

- [Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html)
- [Typelevel Documentation](https://typelevel.org/)
- [Cats Documentation](https://typelevel.org/cats/)
- [Cats Effect](https://typelevel.org/cats-effect/)
- [ZIO Documentation](https://zio.dev/)
- [Scala Style Guide](https://docs.scala-lang.org/style/)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.