JavaRush /Courses /Go SELF /Import path vs package name

Import path vs package name

Go SELF
Level 10, Lesson 2
Available

1. Where the confusion comes from: you import one thing, you write another

When you are just starting to write Go, imports look like simple magic: you write import "fmt" — and you can call fmt.Println. But at some point you will see import "math/rand", and in the code you suddenly have rand.Intn(...). Or even more fun: you will want to use both "math/rand" and "crypto/rand", and it turns out that both packages are called rand “in code.”

To stop “memorizing imports” and start confidently reading other people’s code, you need to separate two distinct concepts:

  • import path — the quoted string by which Go finds a package;
  • package name — the name you use in code as the prefix before the dot ..

Next, we will go through this with examples from the standard library, and in parallel we will slightly improve our learning console app.

2. Import path: the address Go uses to locate a package

Import path is what appears in quotes in import. Important idea: import path is for the compiler and tools, not for you “for beauty.” It answers the question: “where is the source code of the package I want to include?”

What an import path looks like

Here are typical import paths in the standard library:

  • "fmt"
  • "strconv"
  • "strings"
  • "math/rand"
  • "crypto/rand"
  • "net/http"
  • "encoding/json"

Notice: sometimes it is a single word ("fmt"), and sometimes it is a path with / ("math/rand"). This does not mean “a folder on your system right now” (although historically and organizationally it is related). In your head, the import path should live as the “unique address of a package.”

Why this address must be unique

In Go, there is an idea that one import path should unambiguously point to one logical package. Otherwise, you cannot understand what exactly is connected and why it behaves the way it does. This is the basis for an import compatibility rule: if you change a package in an incompatible way, you usually change its import path too (in modules, this became even more visible).

We are not diving into Go Modules, versions, and commands today — we only need the idea: import path is an identifier of a dependency, not a “variable name.”

Mini diagram: what Go does when it sees an import

flowchart LR
    A["main.go file"] --> B["import 'math/rand'"]
    B --> C["Go finds the package by import path"]
    C --> D["The package declares its name: package rand"]
    D --> E["In code, we write rand.Intn(...)"]

Key point: the import path is involved in locating the package, but how you refer to it in code is determined by the package name, not by the path string.

3. Package name: the name you write in pkg.Something

Package name is what appears in the package sources in the line package .... This name becomes the “prefix” before the dot in your code.

For example, when you write:

fmt.Println("hi")

fmt is the package name, and Println is an exported function inside the package.

An important detail that breaks your brain once

Package name does not have to match the import path. Most often, it matches the last path segment, which is why it feels like the same thing. But in general, these are different concepts:

  • import path: "math/rand"
  • package name: rand

So the string "math/rand" is not what you write to the left of the dot.

Table: the difference at a glance

Question Answered by import path Answered by package name
“Where can I find the package?” Yes No
“How do I write the prefix before .?” No Yes
“Is it a quoted string?” Yes No (it is an identifier)
“Can it conflict with another package?” Usually no (more unique) Yes (for example, two packages can be named rand)

4. Examples from the standard library

Now we will do a few small examples (5–10 lines each) so you train your eye: you see import "..." — you understand it is an address; you see something.X — you understand something is the package name (or an alias, but aliases will be a separate topic later).

Example: math/rand → rand

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println(rand.Intn(10)) // e.g.: 7
}

Here it is important to say out loud: we imported by path "math/rand", but in code we use the package name rand.

Example: encoding/json → json

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	b, _ := json.Marshal(map[string]int{"tasks": 3})
	fmt.Println(string(b)) // {"tasks":3}
}

We are not discussing map and JSON in depth yet (that will be later), but the example shows the key idea: import path is "encoding/json", and package name is json. And it is json that appears to the left of the dot.

Example: net/http → http

package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println(http.MethodGet) // GET
}

Again: import path "net/http", and in code http.MethodGet.

Why usually everything matches

A phrase that saves you from future panic: Go relies heavily on conventions, and one of them is “package name usually matches the last segment of the import path.”

This is convenient for three reasons.

  • Code is easier to read: you see "encoding/json" — you expect json.*.
  • IDEs and tools automatically add the needed imports.
  • People do not have to memorize extra mappings.

But it is important to stay grounded: these are still two different mechanisms. And when you encounter code where the package name is “unexpected” (or conflicts), you should not feel like the world is broken. The world is not broken — you just stopped living in beginner examples.

Mini-problem: crypto/rand and math/rand

This is where it gets most interesting, because it is an almost perfect demonstration of “import path vs package name.”

  • "math/rand" — a pseudorandom number generator (fast, convenient, but not for cryptography).
  • "crypto/rand" — cryptographically secure random bytes (slower, but suitable for keys and tokens).

And guess what the packages are called in code? Both are rand.

Let’s try to naively import both:

package main

import (
	"crypto/rand"
	"math/rand"
)

func main() {}

This code will not compile, because in a single file you cannot have two packages with the same import name (and the default import name is the package name). In other words, import paths are different, but the package name is the same — conflict.

And that is the important idea: the conflict happens at the package name level, because that is what you use in code (rand.Something), and the compiler must understand unambiguously which rand you mean.

How to solve this technically — we will cover in the next topic (that is where import aliases appear). But conceptually you should already understand the reason: the problem is not in the paths, but in the names.

5. Mini-project StudyBuddy: intentional imports

So the topic does not remain pure theory, let’s continue our learning console program. Let’s call it StudyBuddy: a small helper that asks for a name and the number of solved tasks, then prints a short report and a “motivational phrase.”

We will need packages:

  • fmt for input/output,
  • strconv — we already know it, but here we could also do without it,
  • math/rand and time — to pick a random phrase.

Step: a skeleton and clear imports

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	fmt.Println("StudyBuddy started") // StudyBuddy started
	_ = rand.Intn(10)
	_ = time.Now()
}

Notice: "math/rand" is the import path, while rand.Intn uses the package name rand. Same with "time" → time.Now.

Step: a function that returns a phrase

We can already write functions, so let’s do it neatly:

package main

import (
	"math/rand"
)

func pickPhrase() string {
	phrases := []string{"Hang in there!", "You got this!", "Just a bit more!"}
	return phrases[rand.Intn(len(phrases))]
}

If you have not covered slices ([]string) in your learning track yet, treat this as a “mini demo” and do not worry. You can replace it with switch and three options, but then the example is less lively. The main point is still about rand.Intn: the name rand comes from the package name.

Step: time as a seed so you do not get the same result every time

package main

import (
	"math/rand"
	"time"
)

func initRandom() {
	rand.Seed(time.Now().UnixNano())
}

Yes, this already looks like “magic spells,” but it very clearly shows that you are taking small pieces of functionality from different packages. And each piece is connected via an import path and used via a package name.

Step: put it together, without overload

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())

	var name string
	var solved int

	fmt.Print("Name and solved: ")
	fmt.Scan(&name, &solved)

	fmt.Printf("%s solved %d tasks. %s\n", name, solved, pickPhrase())
}

If you run it and enter:

Name and solved: Ana 5

then you will get something like:

// Ana solved 5 tasks. You got this!

Here we “spread” responsibility across packages. And that is fine: Go is designed around the idea that you more often “glue” a program together from packages than write everything from scratch.

Where the import paths of your own packages come from

In the standard library, import paths are short and fixed: "fmt", "strings", "net/http". But in real projects you will see imports like "github.com/someone/project/pkg/...". This is not “fashion” or “showing off,” it is a way to make the import path unique in the world.

Historically and conceptually, this is tied to the fact that an import path must uniquely identify a package, and as the ecosystem grew, import compatibility rules and dependency management mechanisms appeared. In discussions about modules and “package uniqueness,” the idea is stated plainly: if you change something incompatibly, change the path too; otherwise you break builds and people.

For today, it is enough to keep a simple picture in mind: for your packages, the import path starts with a “project prefix”, and the package name inside them is whatever is written in package ....

6. Typical mistakes when working with import path and package name

Mistake #1: thinking that import "math/rand" means you should write math/rand.Intn in code.
This is a very human mistake: your brain sees a path and wants to use it as a name. But Go does not work this way: to the left of the dot there is always an identifier (package name or alias), not a string path. So it is rand.Intn, not “a path with a slash.”

Mistake #2: mixing up “what is in quotes” and “what is to the left of the dot,” and trying to rename a package by changing the path string.
Sometimes a beginner thinks: “I want the package to be called random — I’ll write import "math/random".” But the import path must point to a real package. If that path does not exist, the compiler is not a magician. Renaming is done via a different mechanism (we will get there), not by inventing new paths.

Mistake #3: not understanding why "crypto/rand" and "math/rand" conflict.
It feels like: “the paths are different — why is the compiler complaining?” But the compiler complains because in code both packages will be called the same: rand. The conflict is in the package name, not in the import path. When you understand this, the problem stops being mystical and becomes a normal engineering task.

Mistake #4: believing that package name is always equal to the last part of the import path.
In the standard library this is almost always true, so your brain gets used to it. But it is a convention, not a law. The correct habit for reading Go code is: the import path is needed to find the package; what it is called in code is determined by the package ... inside it (or by an import alias).

Mistake #5: “why even use packages, you can put everything in one file.”
You can — while the project is small. But as soon as you have more logic, more functions, and more responsibility, packages become a way to keep order. They do not “make life harder,” they protect you from the situation where “everything depends on everything, and any change breaks everything.” At first, packages feel like bureaucracy. Then you write a 20-file project — and you start respecting bureaucracy.

1
Task
Go SELF, level 10, lesson 2
Locked
Address and name
Address and name
1
Task
Go SELF, level 10, lesson 2
Locked
Protocol constants
Protocol constants
1
Task
Go SELF, level 10, lesson 2
Locked
Server response
Server response
1
Task
Go SELF, level 10, lesson 2
Locked
Package name from path
Package name from path
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION