Ruby on Rails: length vs size vs count with examples

In the world of Ruby on Rails, we’ve got three cool methods for figuring out how big our collections are: “length,” “count,” and “size.” They each have their quirks, and knowing the scoop to keep your code running smoothly is good.

  • length”: it loads all the records into memory first and then does a headcount there. If you plan to use those records later, using “length” to count active records can save you a query.
  • count”: The rebel of the group! It always runs a COUNT query, no matter when you ask. So, you get the real-deal count, but be ready for some extra queries.
  • size”: This one’s a bit of a chameleon. If records are still chillin’ in the database, “size” will run a COUNT query. But if the gang’s already in memory, it does a quick in-memory headcount. It’s like having the best of both worlds, depending on your collection’s vibe.

Oh, and when your collection’s already loaded, “size” and “length” are like twins — same results, different names.

So, let’s dive into the quirks of “length,” “count,” and “size” in Ruby on Rails.

The method .count in Ruby on Rails

.count will ALWAYS run a COUNT query to the database:

users = User.all
users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10

Yep, always! Even if you run .count twice, it will run two queries:

users = User.all; users.count; users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10

.count doesn’t load records in memory, which means that the following will run another query:

users = User.all
users.count
# (...)
users.first
# User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users". "id" ASC LIMIT $1 [["LIMIT", 1]]
# => <User id:...

And it will run a COUNT query even if the records are already loaded:

users = User.all.load
# User Load (0.5ms) SELECT "users". * FROM "users"
# => [#<User id: 1...
users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10

The method .length in Ruby on Rails

On the other hand, .length will load the records in memory (if needed):

users = User.all
# User Load (0.6ms) SELECT "users".* FROM "users" LIMIT $1 [["LIMIT", 11]]
users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10

So if you run .length twice, it will run only one query:

users = User.all; users.length; users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10

This also means that this won’t run another query:

users = User.all; users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10
users.first
# => <User id:...

The method .size in Ruby on Rails

Last, .size runs a COUNT query if the records are not loaded:

users = User.all
# User Load (2.2ms) SELECT "users".* FROM "users"
users.size
# User Count (0.7ms) SELECT COUNT(*) FROM "users"
# => 10

So even if you run .size twice, it will run two queries:

users = User.all; users.size; users.size
# User Count (1.2ms) SELECT COUNT(*) FROM "users"
# User Count (0.4ms) SELECT COUNT(*) FROM "users"
# => 10

But .size will be smart enough not to re-run a query if the collection is already loaded:

users = User.all.load; users.size; users.size
# User Load (0.5ms) SELECT "users".* FROM "users"
# => 10